“存在”在Haskell类型系统中意味着什么?

时间:2011-03-08 16:13:38

标签: haskell exists forall quantifiers

我很难理解与Haskell类型系统相关的exists关键字。据我所知,默认情况下Haskell中没有这样的关键字,但是:

  • data Accum a = exists s. MkAccum s (a -> s -> s) (s -> a)
  • 这样的声明中添加了extensions {。}}
  • 我看过一篇关于它们的文章,并且(如果我没记错的话)它声明类型系统不需要exists关键字,因为它可以通过forall
  • 进行推广

但我甚至无法理解exists的含义。

当我说forall a . a -> Int时,它意味着(根据我的理解,我猜错了),“对于每个(类型)a,都有一个{{1}类型的函数}“:

a -> Int

当我说myF1 :: forall a . a -> Int myF1 _ = 123 -- okay, that function (`a -> Int`) does exist for any `a` -- because we have just defined it 时,它甚至意味着什么? “至少有一种类型exists a . a -> Int,其中有a类型的函数”?为什么会写这样的声明?目的是什么?语义?编译器行为?

a -> Int

请注意,它不是一个可以编译的真实代码,只是我想象的一个例子,然后我听说这些量词。


P.S。我并不是Haskell中的新手(可能就像二年级学生一样),但我对这些事情的数学基础缺乏。

4 个答案:

答案 0 :(得分:23)

我遇到的存在类型的使用是我的code for mediating a game of Clue

我的调解代码就像经销商一样。它并不关心玩家的类型 - 它关心的是所有玩家都实现了Player类型类中给出的钩子。

class Player p m where
  -- deal them in to a particular game
  dealIn :: TotalPlayers -> PlayerPosition -> [Card] -> StateT p m ()

  -- let them know what another player does
  notify :: Event -> StateT p m ()

  -- ask them to make a suggestion
  suggest :: StateT p m (Maybe Scenario)

  -- ask them to make an accusation
  accuse :: StateT p m (Maybe Scenario)

  -- ask them to reveal a card to invalidate a suggestion
  reveal :: (PlayerPosition, Scenario) -> StateT p m Card

现在,经销商可以保留Player p m => [p]类型的玩家列表,但这会收缩 所有球员都属于同一类型。

这过于紧缩。如果我想拥有不同类型的玩家,每个玩家都可以实现 不同,并相互运行它们?

所以我使用ExistentialTypes为玩家创建一个包装器:

-- wrapper for storing a player within a given monad
data WpPlayer m = forall p. Player p m => WpPlayer p

现在我可以轻松保持一个异类的球员名单。经销商仍然可以轻松地与之交互 玩家使用Player类型类指定的接口。

考虑构造函数WpPlayer的类型。

    WpPlayer :: forall p. Player p m  => p -> WpPlayer m

除了前面的forall,这是非常标准的haskell。适用于所有类型 如果p满足合同Player p m,则构造函数WpPlayer会映射p类型的值 值为WpPlayer m的值。

有趣的一点是解构函数:

    unWpPlayer (WpPlayer p) = p

unWpPlayer的类型是什么?这有用吗?

    unWpPlayer :: forall p. Player p m => WpPlayer m -> p

不,不是真的。一堆不同类型p可以满足Player p m合约 具有特定类型m。我们给了WpPlayer构造函数一个特定的 输入p,所以它应该返回相同的类型。所以我们不能使用forall

我们真正可以说的是存在某种类型p,它满足Player p m契约 类型为m

    unWpPlayer :: exists p. Player p m => WpPlayer m -> p

答案 1 :(得分:14)

  

当我说,forall a。 a - > Int,它   意思是(据我所知,   错误的,我猜)“为每一个   (类型)a,有一个功能   输入a - > INT“:

关闭,但不完全。这意味着“对于每种类型a,函数可以被认为具有类型a - > Int”。所以a可以专门用于任何类型的调用者选择。

在“存在”的情况下,我们有:“有一些(特定的,但未知的)类型a,使得此函数具有类型a - > Int”。所以a必须是特定类型,但调用者不知道是什么。

请注意,这意味着此特定类型(exists a. a -> Int)并不是那么有趣 - 除了传递“{”}或{{bottom}之类的“底部”值之外,调用该函数没有用处。 {1}}。更有用的签名可能是undefined。它表示该函数返回特定类型let x = x in x,但您无法知道什么类型。但是你确实知道它是exists a. Foo a => Int -> a的一个实例 - 所以尽管不知道它的“真实”类型,你可以用它做一些有用的事情。

答案 2 :(得分:5)

这意味着“存在一个类型a,我可以在构造函数中为其提供以下类型的值。”请注意,这与我的构造函数中的“a的值Int”不同;在后一种情况下,我知道类型是什么,并且我可以使用我自己的函数,它将Int s作为参数,对数据类型中的值执行其他操作。

因此,从实用的角度来看,存在类型允许您隐藏数据结构中的基础类型,迫使程序员仅使用您在其上定义的操作。它代表封装。

出于这个原因,以下类型不是很有用:

data Useless = exists s. Useless s

因为我无法对价值做任何事情(不完全正确;我可以seq它;我对它的类型一无所知。

答案 3 :(得分:5)

UHC实现exists关键字。以下是其文档中的示例

x2 :: exists a . (a, a -> Int)
x2 = (3 :: Int, id)

xapp :: (exists b . (b,b -> a)) -> a
xapp (v,f) = f v

x2app = xapp x2

另一个:

mkx :: Bool -> exists a . (a, a -> Int)
mkx b = if b then x2 else ('a',ord)

y1 = mkx True -- y1 :: (C_3_225_0_0,C_3_225_0_0 -> Int)
y2 = mkx False -- y2 :: (C_3_245_0_0,C_3_245_0_0 -> Int)

mixy = let (v1,f1) = y1
            (v2,f2) = y2
       in f1 v2

“mixy导致类型错误。但是,我们可以很好地使用y1和y2:”

main :: IO ()
main = do putStrLn (show (xapp y1))
          putStrLn (show (xapp y2))
ezyang也对此发表了很好的博文:http://blog.ezyang.com/2010/10/existential-type-curry/