Haskell 为可选的 Either 数据类型定义 Functor 实例
Haskell defining Functor instance for an alternative Either data type
通过 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)
,如果你改变类型参数的顺序,你的代码将编译通过,
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
类型 class 只知道如何映射类型的最后一个类型参数。所以我们不得不重新调整,以便我们将函数 f
应用于最后一个类型参数的出现。
为什么你只能映射最右边的参数是一个非常深刻和有趣的问题。要理解这一点,您必须了解 种类 ,这是 Haskell 类型系统的高级功能。
在某种意义上,您可以将种类视为类型的 "next level"。输入 class 化值;种类 class 确定类型。所以 "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 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
本身不是有效类型 class 约束,但 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
只知道如何映射最右边的类型参数。作为练习,您可以尝试定义一个映射到 second-to-last 类型参数的 Functor
class。
这是一个很大的话题,所以希望我没有走得太快。不能立即理解类型是可以的——我尝试了几次!如果您希望我进一步解释什么,请在评论中告诉我。
通过 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)
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
类型 class 只知道如何映射类型的最后一个类型参数。所以我们不得不重新调整,以便我们将函数 f
应用于最后一个类型参数的出现。
为什么你只能映射最右边的参数是一个非常深刻和有趣的问题。要理解这一点,您必须了解 种类 ,这是 Haskell 类型系统的高级功能。
在某种意义上,您可以将种类视为类型的 "next level"。输入 class 化值;种类 class 确定类型。所以 "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 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
本身不是有效类型 class 约束,但 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
只知道如何映射最右边的类型参数。作为练习,您可以尝试定义一个映射到 second-to-last 类型参数的 Functor
class。
这是一个很大的话题,所以希望我没有走得太快。不能立即理解类型是可以的——我尝试了几次!如果您希望我进一步解释什么,请在评论中告诉我。