这很好用:
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
?
更新
我想我可以用另一种方式问:
data Foo = Foo String deriving(Show)
let jack = Foo "Jack"
-- Some functory thingy here
putStrLn $ show $ tail <$> jack
-- Foo "ack"
为什么我不能这样做?或者这个用例是否有另一种结构?
答案 0 :(得分:10)
那是因为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
答案 1 :(得分:4)
来自动态语言,您可能会将Functor视为一个容器的东西,fmap
作为一种转换容器内容的方法。但是在类别理论中,可以将Functor视为将类型转换为另一种类型的方法,以及将这些类型的函数转换为另一种类型的函数的方法。
想象一下,你有两个不同的世界,一个是地球,一个虚拟世界,当地球上的每个身体/事物都有一个化身。 Functor不是化身,而是魔杖,它将一切变为其化身,但也将现实世界的每一个功能转化为化身世界中的一个功能。
例如,使用我的魔杖,我可以将人变换为青蛙(或将字符串转换为字符串列表)但我也可以将“改变人类帽子”的功能转换为改变青蛙帽“(或者人均化)用于大写列表中所有String的String。
fmap
是将函数转换为另一个函数的方式:您可以将其视为
一个带有2个参数的函数 - 一个函数和一个容器 - 并将此函数应用于此容器的每个元素
但也作为一个带有1个argmunt的函数 - 一个函数 - 返回一个带容器并返回容器的函数。
从类型创建类型的方式不太明显在第一个示例中,您可能只看到Foo String
作为新类型,但您也可以将Foo
看作是一个超级函数String
并返回一个新类型:Foo String
。那就是* -> * kind
。 Foo不是一种类型,而是一种从类型创建类型的超级函数。
在您的第二个示例中,Foo
不是类型创建者,而只是一个简单类型(种类:*
),因此将其声明为仿函数是没有意义的。
如果你真的想在第二个例子中为普通fmap
定义Foo
,那就是定义一个真正的仿函数并为普通类型创建一个类型别名
data FooFunctor a = FooFunctor a
instance Functor Foofunctor where
fmap f (FooFunctor a) = FooFunctor (f a)
type Foo = FooFunctor String
答案 2 :(得分:3)
fmap
的类型是通用的;你不能限制它:
fmap :: Functor f => (a -> b) -> f a -> f b
那些a
和b
s 必须完全是多态的(在Functor
实例的约束范围内),或者您没有{ {1}}。解释原因的手工方式是因为Functor
必须遵守一些理论规律才能使它们与Haskell的其他数据类型相吻合:
Functor
如果您的数据类型是多种类型的参数化,即:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
您可以为data Bar a b = Bar a b
编写Functor
个实例:
Bar a
您还可以为instance Functor (Bar a) where
fmap f (Bar a b) = Bar a (f b)
编写Bifunctor
个实例:
Bar
......这也必须遵循一些法律(在链接页面上列出)。
编辑:
您可以编写自己的类来处理您正在寻找的行为类型,但它看起来像这样:
instance Bifunctor Foo where
first f (Bar a b) = Bar (f a) b
second f (Bar a b) = Bar a (f b)
但是在这种情况下,为了覆盖所有基础,我们必须为我们可能拥有的“内部类型”(如String)的每一个排列创建新的整个类。
您还可以编写一个类(称为class FooFunctor f where
ffmap :: (String -> String) -> f -> f
),它只允许在数据类型的“内部类型”上使用内部类型(类型Endo
的函数),如下所示:
a -> a
然后,如果您稍微更改了数据类型,并实例化了适当的Endo实例,例如
class Endo f where
emap :: (a -> a) -> f a -> f a
...如果您编写data Foo' a = Foo' a
type Foo = Foo' String
instance Endo Foo' where
emap f (Foo a) = Foo (f a)
类型的函数,则可以保证在使用Foo -> Foo
时保留您正在映射的内部类型的“Stringiness”。快速搜索hayoo表明这种类型的事情是相对常见的做法,但并不真正作为标准类型类存在。
答案 3 :(得分:2)
一个完全符合你要求的课程是MonoFunctor。
type instance Element Foo = String
instance MonoFunctor Foo where
fmap f (Foo s) = Foo (f s)
答案 4 :(得分:1)
head "Jack"
不是字符串"J"
,而是字符'J'
。所以你自己的例子说明了为什么这不起作用; head <$> jack
必须提供Foo 'J'
,这不是Foo
类型的有效值,因为Foo
只能应用于String
值,而不是Char
1}}值。
“这个用例的一些其他构造”是为Foo
定义一个“map”函数,就像你试图定义fmap一样。但该地图功能是 不 fmap,因为它必须具有类型(String -> String) -> Foo -> Foo
。因此,没有必要(或可能)使Foo
成为Functor
的实例并命名您的映射函数fmap
;你想要使用的映射函数根本就不是fmap。
请注意,这意味着您无法映射Foo
值上的任意函数;只接受和返回字符串的函数(所以head
仍然没有)。你也不能将Foo
值传递给接受任何仿函数值的泛型函数;这些函数可能会尝试fmap
Foo
以上不返回字符串的函数;他们被允许这样做,因为他们指定他们需要仿函数,而这正是定义仿函数的原因。