Haskell为另一个Either数据类型定义Functor实例

Mad*_*ote 5 haskell compiler-errors functor type-mismatch either

通过Typeclassopedia进行一些使用类型类的路由。想要替代Either的实例Functor,但即使检查的定义EitherFunctor使我陷入麻烦。有这个,但是不会编译。

data Alt a b = Success a | Failure b deriving (Show, Eq, Ord) 

instance Functor (Alt a) where 
  fmap _ (Failure a) = Failure a
  fmap f (Success x) = Success (f x)  

    • Couldn't match expected type ‘a1’ with actual type ‘a’
      ‘a1’ is a rigid type variable bound by
        the type signature for:
          fmap :: forall a1 b. (a1 -> b) -> Alt a a1 -> Alt a b
        at Brenty_tcop.hs:25:3-6
      ‘a’ is a rigid type variable bound by
        the instance declaration
        at Brenty_tcop.hs:24:10-24
    • In the first argument of ‘f’, namely ‘x’
      In the first argument of ‘Success’, namely ‘(f x)’
      In the expression: Success (f x)
    • Relevant bindings include
        x :: a (bound at Brenty_tcop.hs:26:19)
        f :: a1 -> b (bound at Brenty_tcop.hs:26:8)
        fmap :: (a1 -> b) -> Alt a a1 -> Alt a b
          (bound at Brenty_tcop.hs:25:3)
   |
26 |   fmap f (Success x) = Success (f x) 
Run Code Online (Sandbox Code Playgroud)

Ben*_*son 9

就像@chepner在评论中所说,如果您切换类型参数的顺序,您的代码将被编译,

data Alt b a = Success a | Failure b
Run Code Online (Sandbox Code Playgroud)

或可选择地切换的意义的的Functor实例,以便它映射了Failure和叶Success孤独。

instance Functor (Alt a) where
    fmap f (Success x) = Success x
    fmap f (Failure x) = Failure (f x)
Run Code Online (Sandbox Code Playgroud)

基本上,Functor类型类仅知道如何映射类型的最后一个类型参数。因此,我们必须重新调整内容,以便将函数f应用于最后一个类型参数的出现。


为什么只能映射最右边的参数是一个非常深刻且有趣的问题。要了解这一点,您必须了解种类,这是Haskell类型系统的高级功能。

从某种意义上讲,您可以将类型视为类型的“下一层”。类型对值进行分类;种类对类型进行分类。所以"foo"是一个String,并且String是一种类型。在Haskell中,发音为“ type” *

-- :t in ghci asks for the type of a value-level expression
ghci> :t "foo"
"foo" :: String

-- :k asks for the kind of a type-level expression
ghci> :k String
String :: *
Run Code Online (Sandbox Code Playgroud)

所有普通类型(可以有值的类型)都有一种*。所以String :: *Int :: *Bool :: *,等。

当您开始考虑参数化类型时,事情会变得很有趣。Maybe是不是一个类型本身-你不能有类型的值Maybe,但你可以有Maybe IntMaybe String等,所以Maybe是一种功能-它需要一个类型作为参数,它产生的类型。(使用技术术语Maybe类型构造函数。)

-- Maybe is a function...
ghci> :k Maybe
Maybe :: * -> *

-- and you can apply it to an argument to get a type
ghci> :k Maybe Int
Maybe Int :: *
Run Code Online (Sandbox Code Playgroud)

Alt是一个两参数类型的函数。就像常规值函数一样,在Haskell中使用类型函数进行咖喱处理,因此Alt具有类型* -> * -> *(实际上是* -> (* -> *))。

ghci> :k Alt
Alt :: * -> * -> *
Run Code Online (Sandbox Code Playgroud)

现在,Functor是一个高阶类型函数。它带有一个参数f,它本身是一个类型函数。Functor它本身不是有效的类型类约束,而是Functor f

ghci> :k Functor
Functor :: (* -> *) -> Constraint
Run Code Online (Sandbox Code Playgroud)

这意味着Maybe* -> *对于Functor类型函数,它本身具有一种,是有效的参数。但是Int :: *不是,也不是Maybe Int :: *,也不是Alt :: * -> * -> *。错误消息告诉您种类不匹配:

ghci> :k Functor Int
<interactive>:1:9: error:
    • Expected kind ‘* -> *’, but ‘Int’ has kind ‘*’
    • In the first argument of ‘Functor’, namely ‘Int’
      In the type ‘Functor Int’

ghci> :k Functor Alt
<interactive>:1:9: error:
    • Expecting one more argument to ‘Alt’
      Expected kind ‘* -> *’, but ‘Alt’ has kind ‘* -> * -> *’
    • In the first argument of ‘Functor’, namely ‘Alt’
      In the type ‘Functor Alt’
Run Code Online (Sandbox Code Playgroud)

那种类型的系统可以防止您形成无效的类型,就像类型系统可以防止您写入无效的值一样。如果没有同类系统,并且我们被允许编写instance Functor Alt,它将为以下类型(无意义的)产生以下类型fmap

-- :t in ghci asks for the type of a value-level expression
ghci> :t "foo"
"foo" :: String

-- :k asks for the kind of a type-level expression
ghci> :k String
String :: *
Run Code Online (Sandbox Code Playgroud)

因此,我们需要转变Alt :: * -> * -> *为某种类型* -> *,以便为提出有效的论点FunctorAlt是咖喱类型函数,因此,如果我们给它一个类型参数,我们将获得一个类型函数!

ghci> :k Functor (Alt Int)
Functor (Alt Int) :: Constraint
Run Code Online (Sandbox Code Playgroud)

这就是instance声明说的原因instance Functor (Alt x)-它需要给出Alt一个自变量(在这种情况下,自变量可以是任何类型x,只要其类型为即可*)。现在我们有了fmap :: (a -> b) -> Alt x a -> Alt x b,这是一个有效的类型表达式。

因此,通常而言,创建Functor实例的秘诀就是为您的类型提供参数,直到只剩下一个参数为止。这就是为什么Functor只知道如何映射最右边的类型参数的原因。作为练习,您可以尝试定义一个Functor映射到倒数第二个参数的类。

这是一个很大的话题,希望我不会太快。不立即了解种类是可以的-我花了几次尝试!在评论中让我知道您是否需要我进一步解释。