在制作我的自定义Either
和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 all@(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 all@(Rectangle _ x) = show all ++ " - "++ show x
提前谢谢
答案 0 :(得分:7)
让我们看看专为Either
仿函数设计的fmap类型:
fmap :: (a -> b) -> Either e a -> Either e b
我们可以从fmap f all@(Left _)
中看到,all
的类型为Either e a
。这与Either e b
签名规定的预期结果类型fmap
不匹配,因此fmap f all@(Left _) = all
的输入效果不佳。
使用id
的情况也是如此。
答案 1 :(得分: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
的值也是有效的源代码文本,但这并不意味着已知属于Either () ()
类型的特定值可以使用源文本Left ()
定义的内容可以像Either () Bool
类型一样使用。
如果您有多态值,则可以执行此操作:
λ let l = Left ()
l :: Either () b
λ l :: Either () ()
Left ()
it :: Either () ()
λ l :: Either () Bool
Left ()
it :: Either () Bool
请注意,l
中的原始b
值是多态的;它可以用作任何 b的<{1}}。
但是你的Either () b
案例略有不同。 函数 fmap
在fmap
中是多态的,但其参数的值是“在多态的范围内”;在你有你的论点的时候,f {/ em>的调用者已经选择了b
类型作为某种特定类型,所以它是“某种未知的类型,可以通过任何东西”而不是“任何”我喜欢选择“。无法以某种方式将b
类型的值转换为Either a b
类型的值,因此您必须提取Either a c
值,然后创建包含它的a
答案 2 :(得分:0)
在解释类型错误方面,我没有任何内容可以添加到前两个答案中,但是我想提及Left x :: Either t a
在内存中的表示方式与Left x :: Either t b
相同。这意味着即使类型系统不允许您使用Either t a
代替Either t b
类型的值,由于其他答案已经完全清楚地解释了原因,您可以&# 34;力&#34;它使用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
值而不需要额外的内存分配。