简体   繁体   English

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

[英]Why can't I make Either instance of Functor using id in Haskell?

When making my custom Either , and Functor , just to understand clearer types and typeclasses, I found the following situation: 在制作我的自定义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

The code showed above compiles fine but, if I change it to use id , it doesn't compile: 上面显示的代码编译得很好但是,如果我将其更改为使用id ,则不会编译:

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

Why?? 为什么?? Did I miss something? 我错过了什么? The following code also doesn't work: 以下代码也不起作用:

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

... This seems to me very strange because the code showed below compiles: ...这在我看来非常奇怪,因为下面的代码编译:

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

Thank you in advance 先感谢您

Let's look at the type of fmap specialized for the Either functor: 让我们看一下专门用于Either仿函数的fmap类型:

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

As we can see from this, in fmap f all@(Left _) , the type of all is Either ea . 正如我们由此可以看出,在fmap f all@(Left _)类型allEither ea This doesn't match the intended result type Either eb prescribed by the signature of fmap , so fmap f all@(Left _) = all is not well-typed. 这与预期的结果类型不匹配。由fmap的签名规定的Either eb ,因此fmap f all@(Left _) = all不是很好的类型。

Similarly for the case using id . 对于使用id的情况也是如此。

What you're trying to do boils down to this: 你要做的是归结为:

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

with error: 有错误:

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 is bound to the function argument. left绑定到函数参数。 So the type checker knows it's of type Either a Bool . 所以类型检查器知道它的类型Either a Bool Then it's used as the return value. 然后它被用作返回值。 We know from the type f :: Either a Bool -> Either a () that the return value must be of type Either a () . 我们从类型f :: Either a Bool -> Either a ()知道f :: Either a Bool -> Either a ()得知返回值必须是Either a ()类型。 If left is a valid return value, it's type must match the return type of f . 如果left是有效的返回值,则其类型必须与f的返回类型匹配。 So Either a () must be equal to Either a Bool ; 所以Either a ()必须等于Either a Bool ; it is not, so the type checker rejects the program. 它不是,因此类型检查器拒绝该程序。

In turn, it's basically the same problem as this: 反过来,它基本上是这个问题:

λ 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

We gave l a binding and a type, then tried to use it as a different type. 我们给了l一个绑定和一个类型,然后尝试将它作为一个不同的类型。 That's invalid (and feeding it through id wouldn't change its type either). 这是无效的(通过id提供它也不会改变它的类型)。 Even though Left () is also valid source code text for a value of type Either () Bool , that doesn't mean that a particular value known to be of type Either () () that could be defined with the source text Left () can be used as if it were of type Either () Bool . 尽管Left ()也是类型为Either () Bool的值的有效源代码文本 ,但这并不意味着已知可以使用源文本Left ()定义的类型为Either () ()的特定值Left ()可以像使用类型Either () Bool

If you had a polymorphic value, you could do this: 如果您有多态值,则可以执行以下操作:

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

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

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

Note that the original l value here was polymorphic in b ; 注意,这里的原始l值在b是多态的; it can be used as an Either () b for any b. 它可以用作任何 b的Either () b b。

But your fmap case is subtly different. 但是你的fmap案例却略有不同。 The function fmap is polymorphic in the b , but the value of its argument is "within the scope of the polymorphism"; 函数 fmapb是多态的,但其参数的值是“在多态的范围内”; at the point you have your argument the type b has been chosen to be some specific type by the caller of fmap , so it's "some unknown type that could by anything" rather than "any type I feel like choosing". 在你有你的论证的时候,类型bfmap的调用者选择为某种特定类型,所以它是“某种未知的类型,可以通过任何东西”,而不是“我想选择的任何类型”。 There is no way to somehow turn a value of type Either ab into a value of type Either ac , so you have to extract the a value and then create an Either ac containing it. 有没有办法以某种方式把类型的值, Either ab成类型的值Either ac ,所以你要提取a值,然后创建一个Either ac包含它。

In terms of explaining the type error, I have nothing to add to the previous two answers, however I'd like to mention that Left x :: Either ta is represented in memory the same way as Left x :: Either tb . 在解释类型错误方面,我没有任何内容可以添加到前两个答案中,但是我想提一下, Left x :: Either ta在内存中的表示方式与Left x :: Either tb What this means is that even though the type system does not let you use an Either ta in place of a value of type Either tb , for reasons already explained in perfect clarity by the other answers, you can "force" it through the type checker using unsafeCoerce : 这意味着即使类型系统不允许您使用Either ta代替类型为Either tb的值,由于已经通过其他答案完全清楚地解释的原因,您可以通过类型检查器“强制”它使用unsafeCoerce

import Unsafe.Coerce (unsafeCoerce)

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

And even though unsafeCoerce is generally regarded as something to avoid, if you know what you are doing, and you have a valid reason to do so, such as performance, unsafeCoerce can be useful in letting the compiler know you are sure the runtime value will match the expected structure. 即使unsafeCoerce通常被认为是要避免的东西,如果你知道自己在做什么,并且你有充分的理由这样做,比如性能, unsafeCoerce可以让编译器知道你确定运行时值会有用匹配预期的结构。

In this case, without unsafeCoerce , and not considering any potential GHC optimizations, fmap f (Left x) = Left x would always construct a new but physically identical Left value, whereas the unsafeCoerce flavor would just return the original Left value with no additional memory allocations. 在这种情况下,没有unsafeCoerce ,并且没有考虑任何潜在的GHC优化, fmap f (Left x) = Left x将始终构造一个新的但物理上相同的Left值,而unsafeCoerce flavor只返回原始Left值而没有额外的内存分配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM