[英]Functors don’t work with data types that require specific types
这很好用:
data Foo a = Foo a
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
这会抛出一个错误:
data Foo = Foo String
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
错误:
Kind mis-match The first argument of `Functor' should have kind `* -> *', but `Foo' has kind `*' In the instance declaration for `Functor Foo'
我在这里错过了什么? 如果Foo
拥有特定类型,为什么我不能使用仿函数来包装和展开Foo
?
UPDATE
我想我可以用另一种方式问:
data Foo = Foo String deriving(Show)
let jack = Foo "Jack"
-- Some functory thingy here
putStrLn $ show $ tail <$> jack
-- Foo "ack"
为什么我不能这样做? 或者这个用例有另一种结构吗?
那是因为Foo
需要一个单一的类型变量才能运行。
fmap
的类型是:
fmap :: Functor f => (a -> b) -> f a -> f b
现在尝试将此专门用于你的Foo
:
(a -> b) -> Foo -> Foo
你能看出问题出在哪里吗? 这些类型不仅仅匹配。 所以,要使Foo
成为一个仿函数,它必须是这样的:
Foo a
所以当你专门为fmap它时,它有以下正确的类型:
(a -> b) -> Foo a -> Foo b
来自动态语言,你可能会看到Functor作为东西和fmap
的容器 ,作为转换容器内部事物的方法。 但是在类别理论中,可以将Functor视为将类型转换为另一种类型的方法,以及将这些类型的函数转换为另一种类型的函数的方法。
想象一下,你有两个不同的世界,一个是地球,一个虚拟世界,当地球上的每个身体/事物都有一个化身。 Functor不是化身,而是魔杖,它将一切变为其化身,但也将现实世界的每一个功能转化为化身世界中的一个功能。
例如,使用我的魔杖,我可以将人变换为青蛙(或将字符串转换为字符串列表),但我也可以将“更改人类帽子”的功能转换为更改青蛙帽子“(或者将字符串变为大写列表中的所有字符串)。
fmap
是将函数转换为另一个函数的方式:您可以将其视为
一个带有2个参数的函数 - 一个函数和一个容器 - 并将此函数应用于该容器的每个元素
而且作为一个带有1个argmunt的函数 - 一个函数 - 返回一个带容器并返回容器的函数。
从类型创建类型的方式不太明显在第一个示例中,您可能只看到Foo String
作为新类型,但您也可以将Foo
视为一个超类函数,它采用String
类型并返回一个新类型: Foo String
。 这就是* -> * kind
。 Foo不是类型,而是从类型创建类型的超级函数。
在你的第二个例子中, Foo
不是类型创建者,而只是一个简单类型(kind: *
),因此将它声明为仿函数是没有意义的。
如果你真的想在第二个例子中为普通Foo
定义fmap
,那就是定义一个真正的fmap
函数并为普通类型创建一个类型别名
data FooFunctor a = FooFunctor a
instance Functor Foofunctor where
fmap f (FooFunctor a) = FooFunctor (f a)
type Foo = FooFunctor String
fmap
的类型是通用的; 你不能限制它:
fmap :: Functor f => (a -> b) -> f a -> f b
那些a
S和b
s 必须是完全多态性(在您的约束Functor
实例),或者你没有一个Functor
。 解释原因的手工方式是因为Functor
必须遵守一些理论规则才能使它们与Haskell的其他数据类型相得益彰:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
如果您有一个参数化多种类型的数据类型,即:
data Bar a b = Bar a b
您可以为Bar a
编写Functor
实例:
instance Functor (Bar a) where
fmap f (Bar a b) = Bar a (f b)
您还可以为Bar
编写Bifunctor
实例:
instance Bifunctor Foo where
first f (Bar a b) = Bar (f a) b
second f (Bar a b) = Bar a (f b)
......这也必须遵循一些法律(在链接页面上列出)。
编辑:
您可以编写自己的类来处理您正在寻找的行为类型,但它看起来像这样:
class FooFunctor f where
ffmap :: (String -> String) -> f -> f
但是在这种情况下,为了覆盖所有基础,我们必须为我们可能拥有的“内部类型”(如String)的每一个排列创建新的整个类。
您还可以编写一个类(称为Endo
),它只允许在数据类型的“内部类型”上使用endomorphisms(类型为a -> a
函数),如下所示:
class Endo f where
emap :: (a -> a) -> f a -> f a
然后,如果您稍微更改了数据类型,并实例化了适当的Endo实例,例如
data Foo' a = Foo' a
type Foo = Foo' String
instance Endo Foo' where
emap f (Foo a) = Foo (f a)
...如果您编写类型为Foo -> Foo
函数,则可以保证在使用emap
保留您正在映射的内部类型的“ emap
。 对hayoo的快速搜索显示,这种类型的东西是相对常见的做法,但并不真正作为标准类型类存在。
一个完全符合你要求的类是MonoFunctor 。
type instance Element Foo = String
instance MonoFunctor Foo where
fmap f (Foo s) = Foo (f s)
head "Jack"
不是字符串"J"
,而是字符'J'
。 所以你自己的例子说明了为什么这不起作用; head <$> jack
必须提供Foo 'J'
,这不是Foo
类型的有效值,因为Foo
只能应用于String
值,而不能应用于Char
值。
这个用例的“其他一些构造”是为Foo
定义一个“map”函数,就像你试图定义fmap一样。 但是这个map函数不是 fmap,因为它必须有类型(String -> String) -> Foo -> Foo
。 因此,没有必要(或可能)将Foo
作为Functor
的实例并命名您的映射函数fmap
; 你想要使用的映射函数根本就不是fmap。
请注意,这意味着您无法在Foo
值上映射任意函数; 只接受和返回字符串的函数(所以head
仍然没有)。 你也不能将Foo
值传递给接受任何仿函数值的泛型函数; 这些函数可能会尝试在不返回字符串的Foo
上fmap
函数; 他们被允许这样做,因为他们指定他们需要仿函数 ,这正是定义仿函数的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.