2015-07-22 38 views
6

當製作我的自定義EitherFunctor,只是爲了瞭解更清晰的類型和類型類,我發現了以下情況:爲什麼我無法在Haskell中使用id的Functor實例?

Functor

module Functor (Functor, fmap) where 

import Prelude hiding(Functor, fmap) 

class Functor f where 
    fmap :: (a -> b) -> f a -> f b 

Either

module Either(Either(..)) where 
import Prelude hiding(Either(..), Functor, fmap) 

data Either a b = Left a | Right b deriving(Show) 

instance Functor (Either a) where 
    fmap f (Right x) = Right (f x) 
    fmap _ (Left x) = Left x 

的上面顯示的代碼編譯得很好但是,如果我改變它使用id,它不會編譯:

instance Functor (Either a) where 
    fmap f (Right x) = Right (f x) 
    fmap _ = id 

爲什麼?我錯過了什麼?下面的代碼也不起作用:

instance Functor (Either a) where 
    fmap f (Right x) = Right (f x) 
    fmap f [email protected](Left x) = all 

...這似乎對我很奇怪,因爲該代碼顯示如下編譯:

data Shape = Circle Point Float | Rectangle Point Point deriving (Show) 

data Point = Point Float Float deriving (Show) 

test :: Shape -> String 
test (Circle _ x) = show x 
test [email protected](Rectangle _ x) = show all ++ " - "++ show x 

預先感謝您

+6

帶有'id'的第一種情況在[這個問題]中有很好的解釋(http://stackoverflow.com/questions/8745597/defining-a-function-by-equations-with-different-number-of-參數)。順便說一下,一定要注意並提到你的問題,你會得到具體的錯誤 - 這使得你和答覆者都能夠更輕鬆地找到答案。 – duplode

+1

偉大的一點,我會保持它 – FtheBuilder

+0

如果你有興趣在免費的表達,你可能會認爲這個定義很可愛:'fmap =任何id' – dfeuer

回答

7

你想做些什麼歸結爲:

f :: Either a Bool -> Either a() 
f (Right _) = Right() 
f left = left 

錯誤:

foo.hs:3:7: 
    Couldn't match type ‘Bool’ with ‘()’ 
    Expected type: Either a() 
     Actual type: Either a Bool 
    In the expression: left 
    In an equation for ‘f’: f left = left 
Failed, modules loaded: none. 

left綁定到該函數的參數。所以類型檢查器知道它是Either a Bool。然後它被用作返回值。我們從f :: Either a Bool -> Either a()知道返回值必須是Either a()。如果left是有效的返回值,則其類型必須匹配返回類型f。所以Either a()必須等於Either a Bool;它不是,所以類型檢查程序拒絕該程序。

反過來,它基本上是相同的問題,因爲這樣的:

λ let l = Left() :: Either()() 
l :: Either()() 

λ l 
Left() 
it :: Either()() 

λ l :: Either() Bool 

<interactive>:10:1: 
    Couldn't match type ‘()’ with ‘Bool’ 
    Expected type: Either() Bool 
     Actual type: Either()() 
    In the expression: l :: Either() Bool 
    In an equation for ‘it’: it = l :: Either() Bool 

我們給l綁定和類型,然後試圖用它爲不同的類型。這是無效的(並通過id餵養它也不會改變它的類型)。儘管Left()對於Either() Bool類型的值也是有效的10源代碼文本,但這並不意味着可以使用源文本Left()定義的已知類型爲Either()()的特定值可以像使用鍵入Either() Bool

如果你有一個多態值,你可以這樣做:

λ let l = Left() 
l :: Either() b 

λ l :: Either()() 
Left() 
it :: Either()() 

λ l :: Either() Bool 
Left() 
it :: Either() Bool 

而且,原lb這裏是多態的;它可以作爲Either() b用於任何 b。

但您的fmap情況是微妙的不同。 函數fmapb中是多態的,但其參數的值是「在多態性的範圍內」;在你有論點的時候,b這個類型被fmap的調用者選爲,所以它是「某種未知類型可以被任何東西」,而不是「我喜歡選擇的任何類型」。無法以某種方式將Either a b類型的值轉換爲Either a c類型的值,因此您必須提取a值,然後創建一個包含它的Either a c

+0

你的解釋真的很棒,讓我更好地理解了Haskell如何控制類型。順便說一下,Haskell製造商是否有動機去分析__given__值,以確定它是否可以是返回類型?我認爲這種方法更直接,但我明白爲什麼給定的程序不能編譯。 Haskell不應該成爲程序員最簡單的方式嗎? – FtheBuilder

+3

@FtheBuilder主要是因爲我們使用類型來執行不變量,所以我們不喜歡什麼時候在我們背後的事情完成後我們的類型。一個可比較的,如果更簡單的情況是Haskell中沒有隱式數字轉換。在任何情況下,你的隱式轉換的用途都是有限的,因爲它只能在你對'Left'進行模式匹配時才能工作。沒有總功能'無論是b - >或者c'(「總數」這裏是「在所有情況下都能工作」的技術術語)。 – duplode

+0

@duplode您能否更好地解釋這段話:「主要是因爲我們使用類型來執行不變量,所以我們不喜歡什麼時候在我們背後的事情背後完成我們的類型。」,也許用不同的詞說同一件事 – FtheBuilder

7

讓我們來看看FMAP類型專門爲Either函子:

fmap :: (a -> b) -> Either e a -> Either e b 

我們由此可見,在fmap f [email protected](Left _)中,all的類型爲Either e a。這與fmap的簽名規定的預期結果類型Either e b不匹配,因此fmap f [email protected](Left _) = all不是很好的類型。

類似的情況下使用id

+0

我理解你的答案部分,但我仍然認爲這很奇怪,因爲糾正我,如果我錯了),返回'左x'而不是'全部'也將類型'任一ea',他們是同一件事...我試圖想盡可能最簡單的方式 – FtheBuilder

+2

不,你是錯誤。如果有'x :: e',那麼'Left x :: E c e''可以選擇'c'。如果你設置了'c〜b',你會得到'Left x :: E b'。 – Cactus

+2

@FtheBuilder'id'具有類型'a - > a',返回類型完全匹配**參數的類型。所以它把'b'或'b'映射到另一個'或b'。但是'fmap'的返回類型並不**完全匹配第二個參數,儘管它的一個參數沒有改變。因此使用簡單的'= id'不會工作。同樣使用'all'也不起作用:參數的類型錯誤。 Haskell不能簡單地概括論證的類型以允許你想要的東西。 – Bakuriu

0

在解釋類型錯誤方面,我沒有什麼可以添加到前面的兩個答案,但是我想提到Left x :: Either t a在內存中的表示方式與Left x :: Either t b相同。這意味着即使類型系統不允許使用Either t a代替Either t b類型的值,但由於其他答案已經完全清楚地解釋了原因,您可以使用unsafeCoerce通過類型檢查程序「強制」它:

import Unsafe.Coerce (unsafeCoerce) 

instance Functor (Either t) where 
    fmap f (Right a) = Right (f a) 
    fmap f l   = unsafeCoerce l 

而且即使unsafeCoerce通常被視爲東西,以避免,如果你知道自己在做什麼,和你有一個正當的理由這樣做,如性能,unsafeCoerce可以讓編譯器是有用的知道您確定運行時值將與預期的結構匹配。

在這種情況下,沒有unsafeCoerce,並且不考慮任何潛在的GHC優化,fmap f (Left x) = Left x總是構造一個新的,但物理上相同Left值,而unsafeCoerce味道也只是沒有額外的內存分配返還原物Left值。

相關問題