我試圖理解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屬于Animaltypeclass的函式,它按預期作業。我可以使用getA,只要我提供型別標注類似read。
但是,當我嘗試定義如下所示的獨立函式時,它無法編譯。為什么這是一個錯誤?
getA' :: Animal a => a
getA' = if True then Dog else Cat
為什么獨立函式getA'不起作用而起作用getA?
uj5u.com熱心網友回復:
這是一個很常見的錯誤:忽視了多型的方向。
簡而言之:選擇型別引數的是函式的呼叫者,而不是函式的實作者。
稍微長一點:當你給你的函式一個像 的簽名時,你向你的函式的Animal a => a任何呼叫者做出了承諾,這個承諾的內容是這樣的:“選擇一種型別。任何型別。無論你想要什么型別。我們稱之為a。現在確保有一個實體Animal a。現在我可以回傳一個型別為a“的值”
所以你看,當你撰寫這樣的函式時,你不能回傳你選擇的特定型別。您必須回傳函式的呼叫者稍后在呼叫它時會選擇的任何型別。
用一個具體的例子把它帶回家,想象一下你的getA'函式是可能的,然后考慮這段代碼:
data Giraffe = Giraffe
instance Animal Giraffe where
speak _ = "Huh?"
getA = Giraffe
myGiraffe :: Giraffe
myGiraffe = getA' -- does this work? how?
使用型別類方法這是可行的,因為它與呼叫者呼叫的函式不同。這是兩個不同的函式,一個 forDog和另一個 for 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。所有型別總是已知的,沒有什么是通用的。
Q:好的,但是這樣的話,如果我想加Giraffe,后面就不行了,在另一個模塊中,我必須修改Animal型別。我不能兩全其美嗎?
簡短的回答:沒有。這是一個眾所周知的問題,稱為“運算式問題”,其基本思想是您可以預先知道所有內容(“封閉世界”),或者稍后添加更多內容(“開放世界”),但你不能同時擁有兩者。呸!
But in Haskell, you still sorta can. But not really. This is a bit more advanced, so please ignore if it seems confusing.
What you can do is add another type, which will contain an animal value plus its Animal instance. Both wrapped up in a box. It looks like this:
data SomeAnimal where
SomeAnimal :: Animal a => a -> SomeAnimal
Then you can construct values of this type by wrapping Cat or Dog:
aCat :: SomeAnimal
aCat = SomeAnimal Cat
aDog :: SomeAnimal
aDog = SomeAnimal Dog
Note that both aCat and aDog are of the same type SomeAnimal. This is the key point. They're values of different types wrapped inside the box that looks the same from the outside, and the box also contains their respective Animal instance.
And this means that, if you unbox the box, you get the value and its Animal instance, which in turn means that you get to use the Animal methods. For example:
someSpeak :: SomeAnimal -> String
someSpeak (SomeAnimal a) = speak a
And with this, you can implement your getA' function this way:
getA' :: SomeAnimal
getA' = if True then SomeAnimal Dog else SomeAnimal Cat
However, you still get "The Expression Problem", because I actually lied a little: it's not about "closed world" vs. "open world", it's about extending the set of operations vs. extending the set of possible values. One will always be easy, and the other hard (read the link for details).
And this applies to this case too:
- if you make
CatandDogvalues of the same type, you get to easily add more functions, but if you want to add more animals, you have to find all those functions you already made and modify them. Hard. - if you make them different types and go the
SomeAnimalroute to unify them, you get to easily add more animals - just make a type and implement theAnimalclass. But if you want to add more functions, you have to go through all those animals you already made and add implementations for the new function to each of theirAnimalinstances.
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/359157.html
上一篇:僅回傳字串串列中的元音
