这是一个非常常见的错误:忽略了多态的方向。
简单来说:是函数的调用者选择类型参数,而不是函数的实现者。
稍微详细一点:当您给函数签名添加类似于“Animal a => a”的内容时,您向任何调用您的函数的调用者做出了承诺,该承诺读作:“选择一种类型。任何类型。无论您想要什么类型。让我们称其为'a'。现在确保有一个实例'Animal a'。现在我可以返回类型为'a'的值。”
因此,当您编写这样的函数时,您不能返回您选择的特定类型。您必须返回调用您的函数时调用者将选择的任何类型。
为了通过具体示例来加深理解,请想象您的“getA'”函数是可能的,然后考虑以下代码:
data Giraffe = Giraffe
instance Animal Giraffe where
speak _ = "Huh?"
getA = Giraffe
myGiraffe :: Giraffe
myGiraffe = getA'
使用类型类方法可以实现这一点,因为调用者所调用的不是同一个函数。这是两个不同的函数,一个是针对Dog
,另一个是针对Cat
,只是恰好共享相同的名称。
当调用者开始调用其中一个函数时,他们需要以某种方式选择其中一个。有两种方法可以做到这一点:要么(1)他们知道他们想要的确切类型,然后编译器可以查找相应类型的函数,要么(2)其他人以某种方式将Animal
实例传递给他们,而正是该实例包含对函数的引用。
现在,如果你真正想要创建一个系统,其中只能有有限数量的动物(即仅有
Cat
和
Dog
),并且
getA'
函数将根据原因返回其中之一,则你需要的不是类型类,而只是像这样的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
类型的值。所有类型始终是已知的,没有通用的东西。
问:好的,但是这样,如果我想添加
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
然而,“表达式问题”仍然存在,因为我其实有点撒谎了:它并不是关于“封闭世界”与“开放世界”的区别,而是关于扩展操作集合与扩展可能值集合的区别。其中一个总是容易的,另一个则很难(详见链接)。这也适用于这个案例:
如果你让 “Cat” 和 “Dog” 成为相同类型的值,你可以轻松地添加更多函数,但如果你想要添加更多动物,你必须找到所有那些已经制作好的函数并修改它们。困难。
如果你使它们成为不同类型,并走“SomeAnimal”的路线来统一它们,你可以轻松地添加更多动物 - 只需创建一个类型并实现“Animal”类即可。但如果你想要添加更多功能,你必须浏览所有那些已经制作好的动物,并为每个“Animal”实例添加新功能的具体实现。
doA
是do
的无用使用。不如尝试putAnimal animal = putStrLn $ show animal
。 - Micha Wiedenmann