Return haskell 中的类型多态性
Return type polymorphism in haskell
我正在尝试了解 haskell 中的多态性。给出下面的典型示例
module Main where
data Dog = Dog
data Cat = Cat
class Animal a where
speak :: a -> String
getA :: a
instance Animal Dog where
speak _ = "Woof"
getA = Dog
instance Animal Cat where
speak _ = "Meow"
getA = Cat
doA animal = do
putStrLn $ speak animal
main :: IO ()
main = do
doA Dog
doA Cat
doA (getA :: Dog)
我有 getA
函数,它是 Animal
类型类的一部分,它按预期工作。只要我提供像 read
.
这样的类型注释,我就可以使用 getA
然而,当我尝试定义如下所示的独立函数时,它无法编译。为什么这是一个错误?
getA' :: Animal a => a
getA' = if True then Dog else Cat
为什么独立函数 getA'
不起作用而 getA
起作用?
这是一个很常见的错误:忽略了多态的方向。
简而言之:是函数的调用者选择类型参数,而不是实现者 的函数。
稍微长一些: 当你给你的函数一个像 Animal a => a
这样的签名时,你就是在向你的函数的任何调用者做出承诺,并且这个承诺读取了一些东西像这样:“选择一个类型。任何类型。无论你想要什么类型。我们称之为 a
。现在确保有一个实例 Animal a
。现在我可以 return 你的值类型为 a
"
所以你看,当你写这样的函数时,你不会return你选择的特定类型。您必须 return 您函数的调用者稍后调用时选择的任何类型。
用一个具体的例子来说明问题,想象一下你的 getA'
函数是可能的,然后考虑这段代码:
data Giraffe = Giraffe
instance Animal Giraffe where
speak _ = "Huh?"
getA = Giraffe
myGiraffe :: Giraffe
myGiraffe = getA' -- does this work? how?
使用 class 类型的方法是可行的,因为它与调用者调用的函数不同。这是两个不同的函数,一个用于 Dog
,另一个用于 Cat
,恰好共享相同的名称。
当调用者开始调用这些函数之一时,他们需要以某种方式选择哪一个。这可以通过两种方式完成:(1)他们知道他们想要的确切类型,然后编译器可以查找该类型的相应函数,或者(2)其他人以某种方式传递了一个 Animal
实例对他们来说,就是那个包含对函数的引用的实例。
现在,如果你真正想做的是创建一个系统,其中可以有数量有限的动物(即只有 Cat
和 Dog
),并且 getA'
函数会 return 其中之一,具体取决于原因,那么您要查找的不是类型 class,而只是一个 ADT,如下所示:
data Animal = Cat | Dog
speak :: Animal -> String
speak Cat = "Meow"
speak Dog = "Woof"
getA' :: Animal
getA' = if True then Dog else Cat
在这里,函数 getA'
可以正常工作,因为 Cat
和 Dog
都是相同类型 Animal
的值。所有类型总是已知的,没有什么是通用的。
Q: 好的,但是这样的话,如果我要加Giraffe
,后面就不行了,在另一个模块中,我得修改Animal
类型。我不能两全其美吗?
简答:没有。这是一个众所周知的问题,称为“The Expression Problem”,其基本思想是您可以预先知道一切(“封闭世界”), 或 你得到稍后添加更多东西(“开放世界”),但你不能同时拥有两者。呸!
但是在Haskell,你还是可以的。但不是真的。这有点高级,所以如果看起来令人困惑,请忽略。
你可以做的是添加另一种类型,它将包含一个动物值加上它的Animal
实例。两个都装在一个盒子里。它看起来像这样:
data SomeAnimal where
SomeAnimal :: Animal a => a -> SomeAnimal
然后你可以通过包装 Cat
或 Dog
:
来构造这种类型的值
aCat :: SomeAnimal
aCat = SomeAnimal Cat
aDog :: SomeAnimal
aDog = SomeAnimal Dog
请注意,aCat
和 aDog
都是同一类型 SomeAnimal
。这是关键点。它们是包裹在盒子里的不同类型的值,从外面看起来是一样的,盒子也包含它们各自的 Animal
实例。
这意味着,如果您拆箱,您将获得值及其 Animal
实例,这反过来意味着您可以使用 Animal
方法。例如:
someSpeak :: SomeAnimal -> String
someSpeak (SomeAnimal a) = speak a
有了这个,您可以这样实现 getA'
功能:
getA' :: SomeAnimal
getA' = if True then SomeAnimal Dog else SomeAnimal Cat
然而,你仍然得到“表达式问题”,因为我实际上撒了一点谎:这不是关于“封闭世界”与“开放世界”的问题,而是关于扩展操作集与扩展操作集的问题可能的值。一个总是容易的,另一个总是困难的(阅读 link 了解详情)。
这也适用于这种情况:
- 如果你使
Cat
和 Dog
值相同类型,你可以轻松添加更多功能,但如果你想添加更多动物,你必须找到所有这些功能你已经制作并修改了它们。很难。
- 如果你让它们成为不同的类型并走
SomeAnimal
路线来统一它们,你就可以轻松添加更多动物 - 只需创建一个类型并实现 Animal
class。但是,如果您想添加更多功能,则必须检查您已经制作的所有这些动物,并将新功能的实现添加到它们的每个 Animal
个实例中。
我正在尝试了解 haskell 中的多态性。给出下面的典型示例
module Main where
data Dog = Dog
data Cat = Cat
class Animal a where
speak :: a -> String
getA :: a
instance Animal Dog where
speak _ = "Woof"
getA = Dog
instance Animal Cat where
speak _ = "Meow"
getA = Cat
doA animal = do
putStrLn $ speak animal
main :: IO ()
main = do
doA Dog
doA Cat
doA (getA :: Dog)
我有 getA
函数,它是 Animal
类型类的一部分,它按预期工作。只要我提供像 read
.
getA
然而,当我尝试定义如下所示的独立函数时,它无法编译。为什么这是一个错误?
getA' :: Animal a => a
getA' = if True then Dog else Cat
为什么独立函数 getA'
不起作用而 getA
起作用?
这是一个很常见的错误:忽略了多态的方向。
简而言之:是函数的调用者选择类型参数,而不是实现者 的函数。
稍微长一些: 当你给你的函数一个像 Animal a => a
这样的签名时,你就是在向你的函数的任何调用者做出承诺,并且这个承诺读取了一些东西像这样:“选择一个类型。任何类型。无论你想要什么类型。我们称之为 a
。现在确保有一个实例 Animal a
。现在我可以 return 你的值类型为 a
"
所以你看,当你写这样的函数时,你不会return你选择的特定类型。您必须 return 您函数的调用者稍后调用时选择的任何类型。
用一个具体的例子来说明问题,想象一下你的 getA'
函数是可能的,然后考虑这段代码:
data Giraffe = Giraffe
instance Animal Giraffe where
speak _ = "Huh?"
getA = Giraffe
myGiraffe :: Giraffe
myGiraffe = getA' -- does this work? how?
使用 class 类型的方法是可行的,因为它与调用者调用的函数不同。这是两个不同的函数,一个用于 Dog
,另一个用于 Cat
,恰好共享相同的名称。
当调用者开始调用这些函数之一时,他们需要以某种方式选择哪一个。这可以通过两种方式完成:(1)他们知道他们想要的确切类型,然后编译器可以查找该类型的相应函数,或者(2)其他人以某种方式传递了一个 Animal
实例对他们来说,就是那个包含对函数的引用的实例。
现在,如果你真正想做的是创建一个系统,其中可以有数量有限的动物(即只有 Cat
和 Dog
),并且 getA'
函数会 return 其中之一,具体取决于原因,那么您要查找的不是类型 class,而只是一个 ADT,如下所示:
data Animal = Cat | Dog
speak :: Animal -> String
speak Cat = "Meow"
speak Dog = "Woof"
getA' :: Animal
getA' = if True then Dog else Cat
在这里,函数 getA'
可以正常工作,因为 Cat
和 Dog
都是相同类型 Animal
的值。所有类型总是已知的,没有什么是通用的。
Q: 好的,但是这样的话,如果我要加Giraffe
,后面就不行了,在另一个模块中,我得修改Animal
类型。我不能两全其美吗?
简答:没有。这是一个众所周知的问题,称为“The Expression Problem”,其基本思想是您可以预先知道一切(“封闭世界”), 或 你得到稍后添加更多东西(“开放世界”),但你不能同时拥有两者。呸!
但是在Haskell,你还是可以的。但不是真的。这有点高级,所以如果看起来令人困惑,请忽略。
你可以做的是添加另一种类型,它将包含一个动物值加上它的Animal
实例。两个都装在一个盒子里。它看起来像这样:
data SomeAnimal where
SomeAnimal :: Animal a => a -> SomeAnimal
然后你可以通过包装 Cat
或 Dog
:
aCat :: SomeAnimal
aCat = SomeAnimal Cat
aDog :: SomeAnimal
aDog = SomeAnimal Dog
请注意,aCat
和 aDog
都是同一类型 SomeAnimal
。这是关键点。它们是包裹在盒子里的不同类型的值,从外面看起来是一样的,盒子也包含它们各自的 Animal
实例。
这意味着,如果您拆箱,您将获得值及其 Animal
实例,这反过来意味着您可以使用 Animal
方法。例如:
someSpeak :: SomeAnimal -> String
someSpeak (SomeAnimal a) = speak a
有了这个,您可以这样实现 getA'
功能:
getA' :: SomeAnimal
getA' = if True then SomeAnimal Dog else SomeAnimal Cat
然而,你仍然得到“表达式问题”,因为我实际上撒了一点谎:这不是关于“封闭世界”与“开放世界”的问题,而是关于扩展操作集与扩展操作集的问题可能的值。一个总是容易的,另一个总是困难的(阅读 link 了解详情)。
这也适用于这种情况:
- 如果你使
Cat
和Dog
值相同类型,你可以轻松添加更多功能,但如果你想添加更多动物,你必须找到所有这些功能你已经制作并修改了它们。很难。 - 如果你让它们成为不同的类型并走
SomeAnimal
路线来统一它们,你就可以轻松添加更多动物 - 只需创建一个类型并实现Animal
class。但是,如果您想添加更多功能,则必须检查您已经制作的所有这些动物,并将新功能的实现添加到它们的每个Animal
个实例中。