2015-07-22 58 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值。

相关问题