繁体   English   中英

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

[英]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值传递给接受任何仿函数值的泛型函数; 这些函数可能会尝试在不返回字符串的Foofmap函数; 他们被允许这样做,因为他们指定他们需要仿函数 ,这正是定义仿函数的原因。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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