为什么我不能使用 Haskell 中的 id 创建 Functor 的 Either 实例?
Why can't I make Either instance of Functor using id in Haskell?
在自定义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
提前致谢
让我们看一下专门用于 Either
函子的 fmap 类型:
fmap :: (a -> b) -> Either e a -> Either e b
由此可见,在fmap f all@(Left _)
中,all
的类型是Either e a
。这与 fmap
的签名规定的预期结果类型 Either e b
不匹配,因此 fmap f all@(Left _) = all
类型不正确。
与使用 id
的情况类似。
你想做的事情归结为:
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
类型。然后它被用作 return 值。我们从 f :: Either a Bool -> Either a ()
类型知道 return 值必须是 Either a ()
类型。如果 left
是一个有效的 return 值,它的类型必须匹配 f
的 return 类型。所以 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
类型的值也是有效的 源代码文本 ,但这并不意味着已知的特定值属于 [=可以用源文本 Left ()
定义的 26=] 可以像 Either () Bool
.
类型一样使用
如果你有一个多态值,你可以这样做:
λ let l = Left ()
l :: Either () b
λ l :: Either () ()
Left ()
it :: Either () ()
λ l :: Either () Bool
Left ()
it :: Either () Bool
注意这里原来的l
值在b
中是多态的;它可以用作 any b.
的 Either () b
但是您的 fmap
情况略有不同。 函数fmap
在b
中是多态的,但是其参数的值是"within the scope of the polymorphism";在你提出论点时,fmap 的调用者已将类型 b
选择为某种特定类型 ,因此它是 "some unknown type that could by anything" 而不是 "any type I feel like choosing".无法以某种方式将 Either a b
类型的值转换为 Either a c
类型的值,因此您必须提取 a
值,然后创建包含它的 Either a c
.
在解释类型错误方面,我对前两个答案没有任何补充,但我想提一下 Left x :: Either t a
在内存中的表示方式与 Left x :: Either t b
相同.这意味着即使类型系统不允许您使用 Either t a
代替类型 Either t b
的值,出于其他答案已经非常清楚地解释的原因,您可以 "force" 它通过类型检查器使用 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
flavor 只是 return 原始 Left
值,没有额外的内存分配。
在自定义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
提前致谢
让我们看一下专门用于 Either
函子的 fmap 类型:
fmap :: (a -> b) -> Either e a -> Either e b
由此可见,在fmap f all@(Left _)
中,all
的类型是Either e a
。这与 fmap
的签名规定的预期结果类型 Either e b
不匹配,因此 fmap f all@(Left _) = all
类型不正确。
与使用 id
的情况类似。
你想做的事情归结为:
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
类型。然后它被用作 return 值。我们从 f :: Either a Bool -> Either a ()
类型知道 return 值必须是 Either a ()
类型。如果 left
是一个有效的 return 值,它的类型必须匹配 f
的 return 类型。所以 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
类型的值也是有效的 源代码文本 ,但这并不意味着已知的特定值属于 [=可以用源文本 Left ()
定义的 26=] 可以像 Either () Bool
.
如果你有一个多态值,你可以这样做:
λ let l = Left ()
l :: Either () b
λ l :: Either () ()
Left ()
it :: Either () ()
λ l :: Either () Bool
Left ()
it :: Either () Bool
注意这里原来的l
值在b
中是多态的;它可以用作 any b.
Either () b
但是您的 fmap
情况略有不同。 函数fmap
在b
中是多态的,但是其参数的值是"within the scope of the polymorphism";在你提出论点时,fmap 的调用者已将类型 b
选择为某种特定类型 ,因此它是 "some unknown type that could by anything" 而不是 "any type I feel like choosing".无法以某种方式将 Either a b
类型的值转换为 Either a c
类型的值,因此您必须提取 a
值,然后创建包含它的 Either a c
.
在解释类型错误方面,我对前两个答案没有任何补充,但我想提一下 Left x :: Either t a
在内存中的表示方式与 Left x :: Either t b
相同.这意味着即使类型系统不允许您使用 Either t a
代替类型 Either t b
的值,出于其他答案已经非常清楚地解释的原因,您可以 "force" 它通过类型检查器使用 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
flavor 只是 return 原始 Left
值,没有额外的内存分配。