简体   繁体   English

函数不适用于需要特定类型的数据类型

[英]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; 这些函数可能会尝试在不返回字符串的Foofmap函数; 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM