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

时间:2014-07-11 19:18:58

标签: haskell

这很好用:

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"

为什么我不能这样做?或者这个用例是否有另一种结构?

5 个答案:

答案 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

那些ab 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以上不返回字符串的函数;他们被允许这样做,因为他们指定他们需要仿函数,而这正是定义仿函数的原因。