为什么我不能在Haskell中使用id创建Functor的实例?

时间:2015-07-22 02:21:38

标签: haskell functional-programming typeclass currying

在制作我的自定义EitherFunctor时,为了了解更清晰的类型和类型类,我发现了以下情况:

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

提前谢谢

3 个答案:

答案 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案例略有不同。 函数 fmapfmap中是多态的,但其参数的值是“在多态的范围内”;在你有你的论点的时候,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值而不需要额外的内存分配。