[英]Functors don’t work with data types that require specific types
This works fine: 这很好用:
data Foo a = Foo a
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
This throws an Error: 这会抛出一个错误:
data Foo = Foo String
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
Error: 错误:
Kind mis-match The first argument of `Functor' should have kind `* -> *', but `Foo' has kind `*' In the instance declaration for `Functor Foo'
What am I missing here? 我在这里错过了什么? Why can't I use functors to wrap and unwrap Foo
if it holds a specific type? 如果Foo
拥有特定类型,为什么我不能使用仿函数来包装和展开Foo
?
UPDATE UPDATE
I guess I can ask this another way: 我想我可以用另一种方式问:
data Foo = Foo String deriving(Show)
let jack = Foo "Jack"
-- Some functory thingy here
putStrLn $ show $ tail <$> jack
-- Foo "ack"
Why can't I do this? 为什么我不能这样做? Or is there another construct for this use case? 或者这个用例有另一种结构吗?
That's because Foo
needs a single type variable to operate on. 那是因为Foo
需要一个单一的类型变量才能运行。
The type of fmap
is: fmap
的类型是:
fmap :: Functor f => (a -> b) -> f a -> f b
Now try to specialize this for your Foo
: 现在尝试将此专门用于你的Foo
:
(a -> b) -> Foo -> Foo
Can you see where the problem is ? 你能看出问题出在哪里吗? The types won't just match. 这些类型不仅仅匹配。 So, to make Foo
a functor, it has to be something like this: 所以,要使Foo
成为一个仿函数,它必须是这样的:
Foo a
so that when you specialize it for fmap it has the following proper type: 所以当你专门为fmap它时,它有以下正确的类型:
(a -> b) -> Foo a -> Foo b
Coming from a dynamic language you probably see a Functor as a container of stuff and fmap
as a way to transform things inside the container. 来自动态语言,你可能会看到Functor作为东西和fmap
的容器 ,作为转换容器内部事物的方法。 However in Category theory a Functor can be seen as a way to transform a type into another type, and a way to transform a function on those type to function on the other type. 但是在类别理论中,可以将Functor视为将类型转换为另一种类型的方法,以及将这些类型的函数转换为另一种类型的函数的方法。
Imagine you have 2 differents worlds, one which is earth, and a virtual one when every body/things on earth has an avatar. 想象一下,你有两个不同的世界,一个是地球,一个虚拟世界,当地球上的每个身体/事物都有一个化身。 The Functor is not the avatar but the magic wand which transforms everything to its avatar but also every single function of the real world into a function in the avatar world. Functor不是化身,而是魔杖,它将一切变为其化身,但也将现实世界的每一个功能转化为化身世界中的一个功能。
For example, with my magic wand I can transform a human to a frog (or a String to a list of Strings) but I can also transform the function "change the human hat" to change the frog hat" (or capitaize a String to capitalize all the String within a list). 例如,使用我的魔杖,我可以将人变换为青蛙(或将字符串转换为字符串列表),但我也可以将“更改人类帽子”的功能转换为更改青蛙帽子“(或者将字符串变为大写列表中的所有字符串)。
fmap
is the way you transform a function to another : you can see it as fmap
是将函数转换为另一个函数的方式:您可以将其视为
a function which take 2 arguments - a function and a container - and apply this function to each element of this container 一个带有2个参数的函数 - 一个函数和一个容器 - 并将此函数应用于该容器的每个元素
but also as a function which take 1 argmunt - a function - an return a function which takes a container and return a container. 而且作为一个带有1个argmunt的函数 - 一个函数 - 返回一个带容器并返回容器的函数。
The way you create a type from a type is less obvious In your first example you probably just see Foo String
as new type, but you can also see Foo
as a super function whic take the type String
and return a new type : Foo String
. 从类型创建类型的方式不太明显在第一个示例中,您可能只看到Foo String
作为新类型,但您也可以将Foo
视为一个超类函数,它采用String
类型并返回一个新类型: Foo String
。 That's what the * -> * kind
is. 这就是* -> * kind
。 Foo is not a type but a super function creating type from a type. Foo不是类型,而是从类型创建类型的超级函数。
In your second example, Foo
is not a type creator but just a simple type (kind : *
), therefore it doesn't make sense to declare it as a functor. 在你的第二个例子中, Foo
不是类型创建者,而只是一个简单类型(kind: *
),因此将它声明为仿函数是没有意义的。
If you really want to define fmap
for the plain Foo
in your 2nd example is to define a real functor and create a type alias for the plain type 如果你真的想在第二个例子中为普通Foo
定义fmap
,那就是定义一个真正的fmap
函数并为普通类型创建一个类型别名
data FooFunctor a = FooFunctor a
instance Functor Foofunctor where
fmap f (FooFunctor a) = FooFunctor (f a)
type Foo = FooFunctor String
The type of fmap
is generic; fmap
的类型是通用的; you can't constrain it: 你不能限制它:
fmap :: Functor f => (a -> b) -> f a -> f b
Those a
s and b
s must be completely polymorphic (within the constraints of your Functor
instance), or you don't have a Functor
. 那些a
S和b
s 必须是完全多态性(在您的约束Functor
实例),或者你没有一个Functor
。 The handwavy way of explaining why this is is because a Functor
must obey some theoretical laws to make them play nice with Haskell's other data types: 解释原因的手工方式是因为Functor
必须遵守一些理论规则才能使它们与Haskell的其他数据类型相得益彰:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
If you have a data type that is parameterized over multiple types, ie: 如果您有一个参数化多种类型的数据类型,即:
data Bar a b = Bar a b
You can write a Functor
instance for Bar a
: 您可以为Bar a
编写Functor
实例:
instance Functor (Bar a) where
fmap f (Bar a b) = Bar a (f b)
You can also write a Bifunctor
instance for Bar
: 您还可以为Bar
编写Bifunctor
实例:
instance Bifunctor Foo where
first f (Bar a b) = Bar (f a) b
second f (Bar a b) = Bar a (f b)
...which again must follow some laws (listed on the linked page). ......这也必须遵循一些法律(在链接页面上列出)。
Edit: 编辑:
You could write your own class to handle the type of behavior you're looking for, but it would look like this: 您可以编写自己的类来处理您正在寻找的行为类型,但它看起来像这样:
class FooFunctor f where
ffmap :: (String -> String) -> f -> f
But in this case, we'd have to make new entire classes for every single permutation of "inner types" we might have (like String), in order to cover all bases. 但是在这种情况下,为了覆盖所有基础,我们必须为我们可能拥有的“内部类型”(如String)的每一个排列创建新的整个类。
You can also write a class (call it Endo
) that only permits endomorphisms (functions of type a -> a
) on the "inner type" of a data type, like this: 您还可以编写一个类(称为Endo
),它只允许在数据类型的“内部类型”上使用endomorphisms(类型为a -> a
函数),如下所示:
class Endo f where
emap :: (a -> a) -> f a -> f a
Then, if you changed your data type a bit, and instantiated an appropriate instance of Endo, eg 然后,如果您稍微更改了数据类型,并实例化了适当的Endo实例,例如
data Foo' a = Foo' a
type Foo = Foo' String
instance Endo Foo' where
emap f (Foo a) = Foo (f a)
...if you write functions of type Foo -> Foo
, you're guaranteed to preserve the "Stringiness" of the inner type you're mapping if you use emap
. ...如果您编写类型为Foo -> Foo
函数,则可以保证在使用emap
保留您正在映射的内部类型的“ emap
。 A quick search on hayoo reveals that this type of thing is relatively common practice, but doesn't really exist as a standard type class. 对hayoo的快速搜索显示,这种类型的东西是相对常见的做法,但并不真正作为标准类型类存在。
A class that does pretty much exactly what you're asking for is MonoFunctor . 一个完全符合你要求的类是MonoFunctor 。
type instance Element Foo = String
instance MonoFunctor Foo where
fmap f (Foo s) = Foo (f s)
head "Jack"
is not the string "J"
, but the character 'J'
. head "Jack"
不是字符串"J"
,而是字符'J'
。 So your own example shows why this does not work; 所以你自己的例子说明了为什么这不起作用; head <$> jack
would have to give Foo 'J'
, which is not a valid value of type Foo
, since Foo
can only be applied to String
values, not Char
values. head <$> jack
必须提供Foo 'J'
,这不是Foo
类型的有效值,因为Foo
只能应用于String
值,而不能应用于Char
值。
"Some other construct" for this use case is to define a "map" function for Foo
, exactly as you're trying to define fmap. 这个用例的“其他一些构造”是为Foo
定义一个“map”函数,就像你试图定义fmap一样。 But that map function is not fmap, since it has to have type (String -> String) -> Foo -> Foo
. 但是这个map函数不是 fmap,因为它必须有类型(String -> String) -> Foo -> Foo
。 So there's no need (or possibility) to make Foo
an instance of Functor
and name your mapping function fmap
; 因此,没有必要(或可能)将Foo
作为Functor
的实例并命名您的映射函数fmap
; the mapping function you want to use is simply not fmap. 你想要使用的映射函数根本就不是fmap。
Note that this means you cannot map arbitrary functions over your Foo
values; 请注意,这意味着您无法在Foo
值上映射任意函数; only functions which take and return strings (so head
is still out). 只接受和返回字符串的函数(所以head
仍然没有)。 Nor can you pass Foo
values to generic functions that accept values in any functor; 你也不能将Foo
值传递给接受任何仿函数值的泛型函数; those functions might try to fmap
functions over Foo
that do not return strings; 这些函数可能会尝试在不返回字符串的Foo
上fmap
函数; they're allowed to do this because they specified that they need functors , and that's exactly what defines a functor. 他们被允许这样做,因为他们指定他们需要仿函数 ,这正是定义仿函数的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.