Haskell为替代Either数据类型定义Functor实例

问题描述 投票:5回答:1

通过Typeclassopedia获取一些使用类型类的路由。想要替换Either作为Functor的一个实例,但即使检查Either的定义作为Functor的一个例子让我陷入困境。有这个,但不会编译。

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) 
haskell compiler-errors functor type-mismatch either
1个回答
10
投票

As @chepner says in the comments,如果切换类型参数的顺序,你的代码将编译,

data Alt b a = Success a | Failure b

或者切换Functor实例的含义,以便它映射到Failure并单独留下Success

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

基本上,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 :: *

所有普通类型 - 那些可以有价值的类型 - 都有一种*。所以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 :: *

Alt是一个双参数类型的函数。类型函数在Haskell中进行了调整,就像常规值函数一样,所以Alt有一种* -> * -> *(这实际上意味着* -> (* -> *))。

ghci> :k Alt
Alt :: * -> * -> *

现在,Functor是一个高阶类型的函数。它需要一个参数f,它本身就是一个类型函数。 Functor本身并不是一个有效的类型约束,但Functor f是。

ghci> :k Functor
Functor :: (* -> *) -> Constraint

这意味着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’

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

-- `Alt a` is not a valid type, because its second argument is missing!
fmap :: (a -> b) -> Alt a -> Alt b

所以我们需要将Alt :: * -> * -> *变成一种善良的* -> *,以便为Functor提供有效的论据。 Alt是一个curried类型的函数,所以如果我们给它一个单一的类型参数,我们将得到一个类型函数!

ghci> :k Functor (Alt Int)
Functor (Alt Int) :: Constraint

这就是为什么instance声明说instance Functor (Alt x) - 它需要给Alt一个参数(在这种情况下,参数可以是任何类型x,只要它的类型是*)。现在我们有fmap :: (a -> b) -> Alt x a -> Alt x b,这是一个有效的类型表达式。

因此,一般来说,制作Functor实例的方法是首先为你的类型提供参数,直到它只剩下一个参数。这就是为什么Functor只知道如何映射最右边的类型参数。作为练习,您可以尝试定义一个映射在倒数第二个类型参数上的Functor类。

这是一个很大的话题,所以希望我没有走得太快。没有立即理解种类是可以的 - 它花了我几次尝试!请在评论中告诉我您是否希望我进一步解释。

© www.soinside.com 2019 - 2024. All rights reserved.