通过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)
答案 0 :(得分: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中,“类型”的发音为*
。
-- :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 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中也要进行咖喱处理,因此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
是一个咖喱类型函数,因此,如果我们给它一个单一的类型实参,我们将获得一个类型函数!
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
只知道如何映射最右边的type参数的原因。作为练习,您可以尝试定义一个Functor
类,该类映射到 second -last- type参数。
这是一个很大的话题,希望我不会太快。不立即了解种类是可以的-我花了几次尝试!在评论中让我知道您是否需要我进一步解释。