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

标签 haskell compiler-errors functor type-mismatch either

通过 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) 

最佳答案

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 中,“类型”发音为 * .
-- :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 :: * , ETC。

当您开始考虑参数化类型时,事情会变得有趣。 Maybe本身不是一个类型 - 你不能有 Maybe 类型的值, 但你可以有 Maybe Int , Maybe 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 中是柯里化(Currying)的,就像常规值函数一样,所以 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’

种类系统可以防止您形成无效类型,就像类型系统如何防止您写入无效值一样。如果没有 kind 系统,我们可以写 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是一个柯里化(Currying)类型函数,所以如果我们给它一个类型参数,我们将得到一个类型函数!
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 的配方instance 是首先为您的类型提供参数,直到它只剩下一个参数。这就是为什么Functor只知道如何映射最右边的类型参数。作为练习,您可以尝试定义 Functor映射倒数第二个类型参数的类。

这是一个很大的话题,所以希望我没有走得太快。不马上理解种类也没关系——我试了好几次!如果您希望我进一步解释任何内容,请在评论中告诉我。

关于Haskell 为替代的 Either 数据类型定义 Functor 实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55364861/

相关文章:

java - 误解如何使用构造函数(无参数)

ruby - Kali Linux Rolling : Trying to install Ruby 2.2.2 but getting “__rvm_make -j4” error

c++ - Visual Studio 2010 和 std::function

haskell - 如何在 Haskell 程序中组织文件?

haskell - 如果 Haskell 使用 "!"而不是关键字 "not"作为逻辑非运算符,会有什么影响?

list - haskell (:) and (++) differences

haskell - 尝试将映射应用于 Haskell 中函数的 "inner"参数

c - 如何使用 printf 在 C 中打印括号?

haskell - fmap 的单子(monad) "versions"的名称是什么?

c++ - 函数对象与状态