定义自己的Functor

时间:2017-06-28 17:17:09

标签: haskell types functor instances

我一直试图了解Funask在Haskell中的含义,为此我想要一个Functor的例子,没有任何其他属性。

我提出的工作示例是

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor a $ g (b)

我猜这是一个半实用的Functor,因为左值可以安全地存储,同时操作正确的值。然后我希望在操作之前将左值替换为正确的值。所以...

Main> fmap (+2) MyFunctor 2 5
MyFunctor 5 7

更改实例声明以执行此操作会产生错误。

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor b $ g (b)

我不明白,或者知道要搜索什么来找到类似的问题来帮助我。

C:\Haskell\func.hs:3:28: error:
    * Couldn't match type `a1' with `a'
      `a1' is a rigid type variable bound by
        the type signature for:
          fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
        at C:\Haskell\func.hs:3:3
      `a' is a rigid type variable bound by
        the instance declaration at C:\Haskell\func.hs:2:10
      Expected type: MyFunctor a b
        Actual type: MyFunctor a1 b
    * In the expression: MyFunctor b $ g (b)
      In an equation for `fmap':
          fmap g (MyFunctor a b) = MyFunctor b $ g (b)
      In the instance declaration for `Functor (MyFunctor a)'
    * Relevant bindings include
        b :: a1 (bound at C:\Haskell\func.hs:3:23)
        a :: a (bound at C:\Haskell\func.hs:3:21)
        g :: a1 -> b (bound at C:\Haskell\func.hs:3:8)
        fmap :: (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
          (bound at C:\Haskell\func.hs:3:3)

4 个答案:

答案 0 :(得分:5)

  

然后我希望在操作之前将左值替换为正确的值。

对于Functor来说,这是非法的事情:GHC正确地告诉你这些类型没有成功。特别是,MyFunctor值的左侧部分可能与右侧部分的类型不同,如MyFunctor 5 "hello"中所示。由于fmap必须仅对您的类型的最后一个类型参数进行操作,因此必须单独留下第一部分。让我们看一个更具体的例子。

回想一下fmap的类型是

fmap :: Functor f => (a -> b) -> f a -> f b

假设你有fmap的对象:

obj :: MyFunctor Int String

那么f调用fmap f obj的类型必须是什么?要统一所涉及的类型,我们必须

f :: (String -> a)

fmap f obj :: MyFunctor Int a

所以你可以看到不可能"替换"第一个,Int,字段,第二个字符串字段的前一个值:该地方唯一允许的是之前的内容,一个Int!

现在,您可以想象如果您更改MyFunctor定义以使两个字段具有相同类型,则可以使类型工作正常:

data MyFunctor a = MyFunctor a a

-- Also illegal
instance Functor MyFunctor where
  fmap f (MyFunctor a b) = MyFunctor b (f b)

但您也无法执行此操作,因为bf b可能属于不同的类型,并且调用者可以选择要使用的f,因此您的实现fmap可能不认为它们是相同的。

事实上,对于任何给定的数据类型,Functor最多只有一个合法的定义:如果你找到一个合法的定义,你可以确定任何其他定义都是非法的:要么类型不匹配起来,否则它会打破两个Functor法则之一:

fmap id == id
fmap f . fmap g == fmap (f . g)

如果仍然排列类型,你怎么可能违反其中一条法律?通过操作不属于fmap结构的部分结构。例如,通常会有人写一个(坏!)Functor,如下所示:

data Counter a = Counter Int a

instance Functor Counter where
  fmap f (Counter n x) = Counter (n + 1) (f x)

但是这打破了两个Functor定律,因为它允许你计算被调用fmap的次数,这应该是一个未暴露的细节。

fmap id (Counter 0 0) == Counter 1 0
(fmap tail . fmap tail) /= fmap (tail . tail)

答案 1 :(得分:2)

首先,你真的很想要了解这一点。它是Haskell所有令人惊叹的事物的基础之一。

第二,我认为有更好的方法来理解Functor而不是尝试自己创建一个实例。最好的方法之一是先使用使用 fmap,然后就可以找到许多现有Functor个实例。 Prelude中已经有很多。查看它们的源代码,了解它们是如何实例化的。这将使你对自己的直觉有所了解。

您创建的类型"例如,上面实际上已经在前奏中了。它被称为(,)。 (错误地说这是Either之前的事情 - 它不是,因为那种类型data Either a b = Left a | Right b抱歉。)

你想要做的是先了解Prelude中的大约10个Functor。但是,不要感受Functor部分。确保您了解基础数据类型和结构。出于这个原因,除非你真正理解类型,代数数据类型(总和和产品),多参数类型和类型类(不是如何使用或实例化类型类,只是它们是什么),否则这将是徒劳的。 ,以及该机制如何运作。)

如果你想直接介绍使用这些东西的基础知识,我会说通过我帮助的工作来完成这项工作:http://happylearnhaskelltutorial.com

使用 Functor个实例并创建它们之间存在很大差异。我认为你会对他们的意思有更好的直觉,如果你使用它们的话。只有这样,恕我直言,你应该亲自实例化一个。此时,查阅法律也是一个好主意,因为它们很重要。这应该清除你对它们是什么或它们可能是什么的任何误解。

然而,为了让你走上这条道路,它们是关于在某个结构的最浅层中应用函数。该结构所讨论类型的结构。因此,在List中,它将成为列表中的所有"值" (因为想要一种更好的方式来描述列表),因为如果你看一下List的定义类型,你会发现它实际上是列表中最浅层的。但要理解为什么,你需要真正理解Haskell中的列表类型。

因此,我会查看以下类型的工作方式FunctorMaybeList(又名[]),Either,{{ 1}},Data.Map(->)(,)(玫瑰树),Data.Tree中的Identity类型(只是您可以拥有的最简单的参数化代数数据类型,它包含其值:Control.Monad.Identity)。搜索更多(有堆!)

祝你玩得愉快!

答案 2 :(得分:1)

错误信息非常详细,并讲述了整个故事。

首先,它告诉我们fmap的类型,在该实例中应该是

fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b

因此,如果f是任何函数a1 -> b,我们必须使fmap f具有类型MyFunctor a a1 -> MyFunctor a b

请注意返回类型是MyFunctor a b。实际上,这就是GHC所期望的:

Expected type: MyFunctor a b

但相反,它找到了别的东西:

Actual type: MyFunctor a1 b

怎么可能?好吧,fmap返回MyFunctor b (g b)g b的类型为b(正确,如预期的那样),但b(值)的类型为a1而不是预期的a

更实际上,fmap只能更改b中的MyFunctor a b组件,它不会影响a组件。这可以从fmap的类型签名中看出。

答案 3 :(得分:0)

我认为你不能像那样交换价值观。您已声明MyFunctor包含通用类型ab的两个值。 这些不一定是同一类型。例如,您可以创建一个这样的值:

MyFunctor "foo" 42

当您声明Functor (MyFunctor a)的实例时,您基本上已经说过,对于任何通用类型a,您可以将MyFunctor a b映射到MyFunctor a c

例如,如果aString,如上例所示,如果您有一个函数MyFunctor String b,则可以将MyFunctor String c映射到b -> c

您无法交换参数,因为第一个值必须保留其类型。

如果c是除String以外的任何其他类型,则在上述示例中无法执行此操作。