“存在”是什么?在 Haskell 类型系统中意味着什么?
我正在努力理解与 Haskell 类型系统相关的 exists
关键字。据我所知,Haskell中默认没有这样的关键字,但是:
- 有 扩展,在像这样的声明中添加它们:data Accum a = contains s。 MkAccum s (a -> s -> s) (s -> a)
- 我看过一篇关于它们的论文,并且(如果我没记错的话)它指出
存在
关键字对于类型系统来说是不必要的,因为它可以通过forall
进行泛化,
但我什至无法理解 exists
的含义。
当我说,forall a .一个-> Int
,它的意思是(在我的理解中,我猜是不正确的)“对于每个(类型)a
,都有一个类型为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
当我说存在一个 .一个-> Int
,它到底意味着什么? “至少有一种类型a
,并且有一个a -> Int
类型的函数”?为什么有人会写这样的声明?目的是什么?语义?编译器行为?
myF2 :: exists a . a -> Int
myF2 _ = 123
-- okay, there is at least one type `a` for which there is such function
-- because, in fact, we have just defined it for any type
-- and there is at least one type...
-- so these two lines are equivalent to the two lines above
请注意,它并不是一个可以编译的真实代码,只是我想象的一个例子,然后我听到了这些量词。
PS 我不完全是 Haskell 的新手(也许像二年级学生),但我缺乏这些东西的数学基础。
I'm struggling to understand the exists
keyword in relation to Haskell type system. As far as I know, there is no such keyword in Haskell by default, but:
- There are extensions which add them, in declarations like these
data Accum a = exists s. MkAccum s (a -> s -> s) (s -> a)
- I've seen a paper about them, and (if I recall correctly) it stated that
exists
keyword is unnecessary for type system since it can be generalized byforall
But I can't even understand what exists
means.
When I say, forall a . a -> Int
, it means (in my understanding, the incorrect one, I guess) "for every (type) a
, there is a function of a type 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
When I say exists a . a -> Int
, what can it even mean? "There is at least one type a
for which there is a function of a type a -> Int
"? Why one would write a statement like that? What the purpose? Semantics? Compiler behavior?
myF2 :: exists a . a -> Int
myF2 _ = 123
-- okay, there is at least one type `a` for which there is such function
-- because, in fact, we have just defined it for any type
-- and there is at least one type...
-- so these two lines are equivalent to the two lines above
Please note it's not intended to be a real code which can compile, just an example of what I'm imagining then I hear about these quantifiers.
P.S. I'm not exactly a total newbie in Haskell (maybe like a second grader), but my Math foundations of these things are lacking.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我遇到的存在类型的使用是我的用于调解 Clue 游戏的代码。
我的中介代码有点像经销商。它不关心玩家的类型是什么 - 它只关心所有玩家都实现
Player
类型类中给出的钩子。现在,庄家可以保存
Player pm => 类型的玩家列表。 [p]
,但这会限制所有玩家都属于同一类型。
这太狭窄了。如果我想要有不同类型的玩家,每种都实现了怎么办
不同的方式,并让它们相互对抗?
因此,我使用 ExistentialTypes 为玩家创建包装器:
现在我可以轻松保留异构的玩家列表。经销商仍然可以轻松地与经销商互动
使用
Player
类型类指定的接口的播放器。考虑构造函数
WpPlayer
的类型。除了前面的 forall 之外,这是非常标准的 haskell。适用于所有类型
p 满足契约
Player p m
,构造函数WpPlayer
映射一个p
类型的值为
WpPlayer m
类型的值。有趣的一点是解构函数:
unWpPlayer
的类型是什么?这有效吗?不,不是真的。一堆不同类型的
p
可以满足Player p m
合约具有特定类型
m
。我们为WpPlayer
构造函数提供了一个特定的输入 p,因此它应该返回相同的类型。所以我们不能使用
forall
。我们真正能说的是,存在某种类型 p,它满足
Player p m
合约类型为
m
。A use of existential types that I've run into is with my code for mediating a game of Clue.
My mediation code sort of acts like a dealer. It doesn't care what the types of the players are - all it cares about is that all the players implement the hooks given in the
Player
typeclass.Now, the dealer could keep a list of players of type
Player p m => [p]
, but that would constrictall the players to be of the same type.
That's overly constrictive. What if I want to have different kinds of players, each implemented
differently, and run them against each other?
So I use
ExistentialTypes
to create a wrapper for players:Now I can easily keep a heterogenous list of players. The dealer can still easily interact with the
players using the interface specified by the
Player
typeclass.Consider the type of the constructor
WpPlayer
.Other than the forall at the front, this is pretty standard haskell. For all types
p that satisfy the contract
Player p m
, the constructorWpPlayer
maps a value of typep
to a value of type
WpPlayer m
.The interesting bit comes with a deconstructor:
What's the type of
unWpPlayer
? Does this work?No, not really. A bunch of different types
p
could satisfy thePlayer p m
contractwith a particular type
m
. And we gave theWpPlayer
constructor a particulartype p, so it should return that same type. So we can't use
forall
.All we can really say is that there exists some type p, which satisfies the
Player p m
contractwith the type
m
.接近,但不完全。它的意思是“对于每个类型a,这个函数可以被认为具有类型a -> Int”。因此
a
可以专门化为调用者选择的任何类型。在“exists”情况下,我们有:“存在某种(特定但未知)类型 a,使得该函数具有类型a-> Int”。所以
a
一定是一个特定类型,但调用者不知道是什么。注意,这意味着这个特定类型(
存在 a.a -> Int
) 并不是那么有趣 - 除了传递“底部”值(例如undefined
或let x = x in x
)之外,没有任何有用的方法来调用该函数。更有用的签名可能是 Foo a => Int -> a ,它表示该函数返回特定类型a
。但你确实知道它是 Foo 的一个实例 - 所以你可以用它做一些有用的事情,尽管不知道它的“真实”类型。Close, but not quite. It means "for every type a, this function can be considered to have type a -> Int". So
a
can be specialized to any type of the caller's choosing.In the "exists" case, we have: "there is some (specific, but unknown) type a such that this function has the type a -> Int". So
a
must be a specific type, but the caller doesn't know what.Note that this means that this particular type (
exists a. a -> Int
) isn't all that interesting - there's no useful way to call that function except to pass a "bottom" value such asundefined
orlet x = x in x
. A more useful signature might beexists a. Foo a => Int -> a
. It says that the function returns a specific typea
, but you don't get to know what type. But you do know that it is an instance ofFoo
- so you can do something useful with it despite not knowing its "true" type.它的确切含义是“存在一个类型
a
,我可以在构造函数中为其提供以下类型的值”。请注意,这与“在我的构造函数中a
的值是Int
”不同;在后一种情况下,我知道类型是什么,并且我可以使用我自己的函数,该函数将 Int 作为参数来对数据类型中的值执行其他操作。因此,从实用的角度来看,存在类型允许您隐藏数据结构中的基础类型,迫使程序员仅使用您在其上定义的操作。它代表封装。
正是由于这个原因,以下类型不是很有用:
因为我无法对该值做任何事情(不完全正确;我可以
seq 它);我对其类型一无所知。
It means precisely "there exists a type
a
for which I can provide values of the following types in my constructor." Note that this is different from saying "the value ofa
isInt
in my constructor"; in the latter case, I know what the type is, and I could use my own function that takesInt
s as arguments to do something else to the values in the data type.Thus, from the pragmatic perspective, existential types allow you to hide the underlying type in a data structure, forcing the programmer to only use the operations you have defined on it. It represents encapsulation.
It is for this reason that the following type isn't very useful:
Because there is nothing I can do to the value (not quite true; I could
seq
it); I know nothing about its type.UHC 实现exists 关键字。这是其文档中的一个示例
,还有另一个:
“mixy 导致类型错误。但是,我们可以很好地使用 y1 和 y2:”
ezyang 也对此发表了很好的博客:http://blog.ezyang.com/2010/10/existential-type-curry/
UHC implements the exists keyword. Here's an example from its documentation
And another:
"mixy causes a type error. However, we can use y1 and y2 perfectly well:"
ezyang also blogged well about this: http://blog.ezyang.com/2010/10/existential-type-curry/