我很难理解与Haskell类型系统相关的exists
关键字。据我所知,默认情况下Haskell中没有这样的关键字,但是:
data Accum a = exists s. MkAccum s (a -> s -> s) (s -> a)
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中的新手(可能就像二年级学生一样),但我对这些事情的数学基础缺乏。
答案 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/