31

Learn You a Haskell有一个关于函子的例子。我可以阅读 LYAH 和文字,并弄清楚应该发生什么——但我知道的内容还不够多,无法写出这样的东西。我经常在 Haskell 中发现这个问题。

instance Functor (Either a) where  
    fmap f (Right x) = Right (f x)  
    fmap f (Left x) = Left x

但是,我很困惑..为什么这不完整

instance Functor (Either a) where  
    fmap f (Right x) = Right (x)  
    fmap f (Left x) = Left (f x)

如果f没有在顶级定义中使用,那么还有什么限制x使其无法满足Left

4

10 回答 10

42

这是函子类:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

请注意,“f”本身是一个类型构造函数,因为它应用于 fmap 行中的类型变量。这里有一些例子可以说明这一点:

类型构造函数:

IO
Maybe
Either String

类型:

IO Char
Maybe a
Either String String

“Maybe a”是具有一个类型构造函数(“Maybe”)和一个类型变量(“a”)的类型。它还不是具体的东西,但它可用于多态函数的类型签名。

“Either”是一个接受两个类型参数的类型构造函数,所以即使你应用了一个(例如Either String,它仍然是一个类型构造函数,因为它可以接受另一个类型参数。

这一点是:当你定义一个Functor实例时,类型构造函数f不能改变。 这是因为它由相同的变量 表示f,作为 的参数和结果fmap。唯一允许更改的类型是应用于f构造函数的类型。

在你写的时候instance Functor (Either c),是在声明中到处Either c填的。这为这个实例提供了以下类型的 fmap:ffmap

fmap :: (a -> b) -> (Either c) a -> (Either c) b

有了 的定义Either,获得此类型的唯一有用方法是将 Right值应用于函数。请记住,“Either”有两个可能具有不同类型的值。这里的Left值的类型是'c',所以你不能将它应用到函数(它需要一个'a')[1],结果也不正确,因为你会留下Either b a,这不会'不匹配类定义。

在将“f”替换为“Either c”以获得上述带有“Either c”实例的fmap的类型签名之后,接下来是编写实现。有两种情况需要考虑,左派和右派。类型签名告诉我们左侧的类型“c”不能更改。我们也没有任何方法可以更改该值,因为我们不知道它实际上是什么类型。我们所能做的就是别管它:

fmap f (Left rval) = Left rval

对于右侧,类型签名表明我们必须将类型为“a”的值更改为类型为“b”的值。第一个参数是一个函数来做这件事,所以我们使用这个函数和输入值来获得新的输出。将两者放在一起给出了完整的定义

instance Functor (Either c) where
  fmap f (Right rval) = Right (f rval)
  fmap f (Left lval) = Left lval

这里有一个更一般的原则在起作用,这就是为什么编写一个调整左侧的 Functor 实例是不可能的,至少在 Prelude 定义中是不可能的。从上面复制一些代码:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

instance Functor (Either c) where ...

即使我们在实例定义中有一个类型变量“c”,我们也不能在任何类方法中使用它,因为它没有在类定义中提及。所以不能写

leftMap :: (c -> d) -> Either c a -> Either d a
leftMap mapfunc (Left x) = Left (mapfunc x)
leftMap mapfunc (Right x) = Right x

instance Functor (Either c) where
  --fmap :: (c -> d) -> Either c a -> Either d a
  fmap = leftMap

leftMap 和 fmap 的结果是 now (Either d) a。已(Either c)更改为(Either d),但这是不允许的,因为无法在 Functor 类中表达它。为了表达这一点,您需要一个具有两个类型变量的类,例如

class BiFunctor f where
  lMap :: (a -> b) -> f a c -> f b c
  rMap :: (c -> d) -> f a c -> f a d
  biMap :: (a -> b) -> (c -> d) -> f a c -> f b d

在这个类中,由于左右类型变量都在范围内,因此可以编写在任一(或两侧)操作的方法。

instance BiFunctor Either where
  lMap = leftMap
  rMap = rightMap  --the same as the standard fmap definition
  biMap fl fr e = rMap fr (lMap fl e)

尽管在实践中人们通常只为 BiFunctor 类编写“biMap”,如果需要左映射或右映射,则将“id”用于其他函数。

[1] 更准确地说,Left 值的类型为“c”,该函数需要一个“a”,但类型检查器无法统一这些类型,因为“c”类型不在类定义的范围内。

于 2010-07-02T10:06:52.540 回答
9

Left 和 Right 不是类型,并且Left x属于Right y同一类型。它们只是 Either 的构造函数。你可以考虑

Left :: c -> Either c d
Right :: d -> Either c d

您可以有 2 个fmap声明,因为我们知道 Left's 和 Right's 是不同的值。就像

g :: Int -> Int
g 1 = 2
g 2 = 4
g n = n

在这里,我们不能说 1 和 2n是不同的“类型”,只是因为模式匹配有效。


Functor 类是这样定义的

class Functor f where
  fmap :: (a -> b) -> f a -> f b

请注意,aandb是任意类型。为清楚起见,让我们将a实例中的 重命名为c,将函数重命名ffunc

instance Functor (Either c) where  
    fmap func (Right x) = Right (x)  
    fmap func (Left x) = Left (func x)

假设您的 Either 遵循默认定义

data Either c d = Left c | Right d

那么根据你的定义,

fmap    func     (Right x) = Right x
-- # (a -> b) ->      f a       f  b
-- # f = Either c

这股力量a = b,和

fmap    func     (Left x) = Left (func x)
-- # (a -> b) ->     f a       f b
-- # f = Either c

部队c = a = b。考虑到 ,两者都是无效的ab并且c是独立的任意类型。

于 2010-07-01T06:36:34.857 回答
8

好的,这是另一个非常简单的尝试。

你问为什么这不能编译:

instance Functor (Either a) where
    fmap f (Right x) = Right (x)
    fmap f (Left x) = Left (f x)

因此,让我们通过尝试定义相同的函数而不将其作为类实例声明的一部分来尝试简化问题:

这给了我们

foo f (Right x) = Right (x)
foo f (Left x) = Left (f x)

确实可以编译。ghci 告诉我们类型签名:

*Main> :t foo
foo :: (t1 -> a) -> Either t1 t -> Either a t

我们将重命名一些变量以获得更统一的外观:

foo :: (a -> b) -> Either a c -> Either b c

这很有意义。它接受一个函数并将其应用于 Either 的左侧。

但是 fmap 的签名是什么?

*Main> :t fmap
fmap :: (Functor f) => (a -> b) -> f a -> f b

所以让我们替换签名Either c中的 f (我重命名为以防止我们的两个不同的 s 混淆):fmapEither aEither ca

fmap :: (a -> b) -> Either c a -> Either c b

你看到问题了吗?您的函数是完全有效的——它只是具有不同于 fmapEither a 必须具有的类型。

这是关于类型的一种美妙的事情。给定 的签名,对于Either afmap实际上只有一个有意义的实现。fmap

有时,当我们足够幸运和小心时,我们可能会遇到类似的情况——给定一个类型签名,函数几乎可以自己编写。

编辑:尝试回答以下问题。

1)没有“两个功能的组合”在进行。要获得fmapover的类型签名,Either a只需通过fmap函数签名,在你看到的每个地方f,将其替换为Either a. 我们称其为 fmap 类型签名的“特化”。也就是说,它严格来说不如普通类型的 fmap 通用——任何需要更专业类型的函数的地方,你都可以毫无问题地传入通用类型的东西。

2)您的左侧映射函数(在上面的示例中我将其命名为“foo”)就可以了。它工作正常,它做你想要的。您只是不能命名它fmap并在 Functor 实例中使用它。就个人而言,我会将其命名为onLeftor mapLeft

以下所有内容均可忽略/仅供参考,但不作为近期阅读/实际使用的建议:

如果你想变得非常技术,因为你可以同时映射左侧和右侧(尽管你只能为后者声明 Functor),那么 Either 不仅是 Functor,而且是 Bifunctor。这在例如ekmett 的Category-Extras 库中提供(参见http://hackage.haskell.org/packages/archive/category-extras/0.44.4/doc/html/Control-Bifunctor.html)。

有很多很酷的东西涉及使用程序进行计算,以及更严格地使用双函子的“折纸编程”。你可以在这里阅读:http: //lambda-the-ultimate.org/node/1360。但是,您可能不想这样做,至少在您更加熟悉 Haskell 之前是这样。它是计算机科学、数学、研究和非常酷的,但对于理解惯用的 Haskell 编程来说完全没有必要。

于 2010-07-01T17:13:44.417 回答
3

信不信由你,这不是魔术。这一切都与Either a b类型与Either b a. 这是Prelude中Either的定义

data  Either a b  =  Left a | Right b

... 注意类型变量 a 是如何出现的,然后是 b,并且还注意到我们只在 Either Functor 的声明中包含了 a:

instance Functor (Either a) where  
    fmap f (Right x) = Right (f x)  
    fmap f (Left x) = Left x

现在让我们看一下 Maybe Functor 的定义

instance Functor Maybe where
    fmap = map

这里没有类型变量,虽然Maybe接受一个类型参数(如Maybe Int)。我想要得到的是类型不是函子,类型构造函数是函子(函子有 kind *->*)。

所以让f :: b -> c,在适用的 Either Functor 版本中,xfrom(Left x)是 type a,这很好,因为它(Either a)是 functor,xin(Right x)是 Typeb所以(Right x)是 type ((Either a) b),并且(Right (f x))是 type ((Either a) c),因此 fmap 是 type (b->c) -> ((Either a) b) -> ((Either a) c),按要求。

在您失败的版本中,我们认为xin(Right (x))不是 type a,而是 type ,b因此(Right (x))不是((Either a) c)适合 fmap 类型的 type 。

所以总结一下:可以工作的 fmap 出来了 : (b -> c) -> (Either a) b -> (Either a) c,但是不能工作的 fmap 出来了:(b -> c) -> (Either b) a -> (Either c) a这不是 fmap 的正确类型。

于 2010-07-03T01:07:19.550 回答
3

虽然我最终会坚持你的格式,但我将从稍微不同的格式开始,因为我认为这会使我的解释更清楚。

让我们考虑一个不同的数据类型

data Choice a = Default Integer | Chosen a

-- This corresponds to your top, working, instance.
instance Functor Choice where
  fmap f (Default i) = Default i
  fmap f (Chosen  a) = Chosen  (f a)

应该清楚为什么这个实例有效。但是,以下情况如何:

-- Broken!
instance Functor Choice where
  fmap f (Default i) = Default (f i)
  fmap f (Chosen  a) = Chosen  a

您应该能够看到为什么这不起作用。的类型fmapFunctor f => (a -> b) -> f a -> f b; 在这种情况下,它是(a -> b) -> Choice a -> Choice b. 因此,f参数的类型为a -> b。但是,在第二个(失败的)实例声明中,您编写f i. 我们知道,由于数据类型声明,它i必须是一个Integer,所以我们不能应用f它。同样,既然a有 type aChosen a就会有 type Chosen a,而不是 type Chosen b。因此,Functor底部的实例无法工作。

好吧,您的顶级实例Either有效,因为就像在Choice示例中一样,它遵循类型。让我们看看它,有一些重命名:

instance Functor (Either c) where  
  fmap f (Left  c) = Left  c
  fmap f (Right a) = Right (f a)

这个实例声明没有声明Functorfor的实例Either——它不能。作为实例的东西Functor必须采用一个类型参数。因此,Int不能是函子,因为Int不接受类型参数,但[]Maybe可以是,因为[a]Maybe a是完整的类型。 Either但是,有两个类型参数:Either a b. 因此,这个实例所做的是声明它Either c是任何可能的函子c。这在实例声明期间c固定的。所以让我们来看看并添加类型(这不是合法的语法!):

instance Functor (Either c) where
  fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
  fmap f (Left  (c :: c)) = Left  c
  fmap f (Right (a :: a)) = Right (f a :: b)

既然f有类型a -> b,但c类型是固定的c,我们不可能写Left (f c);即使我们可以,我们也希望c不被打扰,这样我们就可以返回一个(Either c) b. 同样,我们必须应用ftoa才能获得 type 的东西b

这也是您的底部实例不起作用的原因:您有一个函数需要适用于仅应用于固定类型的任何类型c,而您需要单独转换的类型。让我们看一下,再次添加类型签名:

instance Functor (Either c) where  
  fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
  fmap f (Left  (c :: c)) = Left  (f c)
  fmap f (Right (a :: a)) = Right a

在这里,函数定义的第一部分尝试将函数应用于f :: a -> b固定类型的东西c,这不能工作,所以这已经失败了。但是让我们看看这会生成什么类型​​。在这种情况下,我们希望(不知何故)f c会有 type b,并且a会有 type a。在这种情况下,我们将返回一个 type 的值Either b a,这仍然是不允许的。

基本上,问题源于此。首先,注意f两个函数定义子句之间是一样的,所以它不能在行之间改变。其次,请注意我们正在修复并为此c声明c一个实例。对于 any 来说都是如此c,但我们一次只看一个。最后,正因为如此,Left' 的参数没有f被期望的类型参数化;它保证有一些固定的类型c。这意味着(a)您不能将其应用于f它,并且(b)您必须将其应用于Right的参数,否则您将不会更改您期望更改的类型。

于 2010-07-01T18:27:52.890 回答
3

(编辑以尝试更好地回答问题)

任何一个的定义是:

data Either a b = Left a | Right b

所以“Either”有两个类型参数。顺便说一句,从技术上讲,“Either”实际上不是类型,而是类型构造函数;它需要类型参数来创建类型。

函子的定义是:

class Functor f where
   fmap :: (p -> q) -> f p -> f q

因此,在此类定义中,作为 Functor 实例的任何类型“f”都必须采用类型参数。这没有声明;它是从“f p”和“f q”推断出来的;因为“f”在这里被赋予了一个类型参数,所以它必须是一个接受一个的类型。

(注意:原始定义使用“a”和“b”而不是“p”和“q”。稍后我会使用不同的字母来区分“Either a b”)

在大多数情况下,“f”是一种容器类型,如列表或树。所以例如你有

data Tree a = ...

instance Functor Tree where
   fmap func_a2b tree_of_a = ... --  tree of b.

但是“Either”有两个类型参数,那么我们如何才能将它放入这个方案中呢?答案是类型可以像函数一样有部分应用。就像我可以声明一个函数一样

foo x y = ...

然后说“foo 2”以获得一个需要第二个参数的新函数,所以我可以说“Either a”来获得一个需要第二个类型参数的新类型。

现在查看原始实例:

instance Functor (Either a) where ....

所以这里的“Either a”是一个类型构造函数,它需要一个参数,就像 Functor 期望它的实例一样。所以“Either a”的“fmap”类型将是

fmap :: (p -> q) -> Either a p -> Either a q

因此,现在在“where”子句中,您必须给出具有这种类型的“fmap”的定义。您引用的第一个具有这种类型,因为第二个类型参数用于“Right”构造函数,并且该函数应用于该构造函数。第二个不起作用,因为它会有类型

fmap :: (p -> q) -> Either p a -> Either q a

这不是 Functor 类所说的那样。

于 2010-07-01T14:59:40.467 回答
2

Top 有效,因为fmap::(b -> c) -> Either a b -> Either a c您的 -bottom- 无效,因为那需要fmap::(a -> c) -> Either a b -> Either a c. 但是,如果您将 Either 更改为

data Either' a b = Left' b | Right' a deriving (Eq, Show)

instance Functor (Either' a) where  
    fmap f (Right' x) = Right' (x)  
    fmap f (Left' x) = Left' (f x)
于 2010-07-02T11:37:16.913 回答
2

希望这会有所帮助...

不过,首先,一些背景:

1) Functor 需要一个“类型构造函数”,一个(嗯,不是类型本身,...)类型,它“需要”另一个常规类型来形成一个“完整类型”,就像 Java 中的泛型一样/ C++。因此,例如,List是一个 Functor(顺便说一句,它是),或者Array,因为(除其他外)完整类型不仅仅是List,而是List<A>。所以,: Functor 采用“类型构造函数”,一个不完整的类型。

2)Either是一种构造函数类型,Haskell 的人(阅读:Edward Kmett 和其他数学天才)称其为双函子。它需要两种类型才能完成。例如,Either 的完整用法是:Either Integer String这意味着(是的,是的,“duh!”)它要么是 ( Left) 整数,要么是 ( Right) 字符串。因此,这意味着当您决定“b”应该是什么时,Either Integer它是一个不完整的类型,要么是 a 要么是 a Left IntegerRight...b

现在是有趣的部分!

最重要的东西之所以起作用,是因为fmap它使用了某种类型的构造函数,并将它与一个a -> b函数一起使用来创建一个类似的函数 from f ato f b- Haskell 中最喜欢的例子是列表的例子,A​​KA map :: (a -> b) -> ([a] -> [b]),其中 Functor 是[ ]一部分. 现在,使用类似Either a(让我们继续使用Either Integer之前的内容),fmap 的类型签名如下所示:

fmap :: (a -> b) -> (Either Integer a -> Either Integer b)

并且两个示例(来自顶部)显示了 fmap 对该Either Integer a类型的代表性值所做的事情,以便获得Either Integer b-typed 值。

现在,你的 -bottom- 不起作用,因为:

  1. 你有一个函数 ,f它需要 as 到bs。
  2. 您的Left类型必须是 Integer 类型,并且保持 Integer 类型(或类型 Float,并且保持 Float,无论哪种类型都是您正在使用的类型的两种类型中的左一种)。Either
  3. 您的Right类型必须是函数采用的任何类型(“ a”),并转到函数创建的类型(“ b”)。

它必须这样做(但你的东西没有 - 这就是它不起作用的原因),因为这是fmap需要的类型。具体来说,您有以下等式:

fmap f (Right x) = Right (x)  
fmap f (Left x) = Left (f x)

你的方程给出fmap了类型:

fmap :: (a -> b) -> Either c d -> Either c d
fmap :: (a -> b) -> Either a d -> Either b d

这不仅不符合fmap想要的,而且甚至彼此不一致!

很抱歉我写了半本书来涉水阅读,但我希望这能给你一些启示。

于 2010-07-01T16:35:42.873 回答
1

嗯...关于“种类”的几句话怎么样?..
我想这可能会简化理解。
记住什么是咖喱。即在ghci中:

Prelude> 让 fxyz = x + y * z
f :: (Num a) => a -> a -> a -> a
前奏>:tf 1
f 1 :: (Num t) => t -> t -> t
前奏>:tf 1 2
f 1 2 :: (Num t) => t -> t
前奏> :tf 1 2 3
f 1 2 3 :: (Num t) => t

与类型相同的东西。当你说Either那种类型是* -> * -> *(即它需要两种类型并产生类型)时,当你说Either a那种类型是* -> *并且因为Either a b它是*(顺便说一句Monad a ,我记得Functor a需要a是那种类型)。* -> *因此,当您说类型时Either a,这意味着类型仍然不完整(需要绑定一些“参数”),所以当fmap :: (a -> b) -> f a -> f b替换为.fmap :: (a -> b) -> (Either c) a -> (Either c) bfEither c

于 2010-07-01T20:00:25.637 回答
1

您尝试编写的实例(我们fmap2现在称之为)具有以下类型:

fmap2 :: (a -> b) -> Either a c -> Either b c

如果你设置了LANGUAGEpragma TypeOperators,GHC 也接受类型

fmap2 :: (a -> b) -> (a `Either` c) -> (b `Either` c)

在理想的世界中,这也可行:

fmap2 :: (a -> b) -> (`Either` c) a -> (`Either` c) b

这将给出一个 Functor 实例,(`Either` c)但此时普通运算符(及其部分)和类型运算符之间的相似性会崩溃(除非我缺少 GHC 选项!)

简而言之:你对函子的理解还可以,但是你被缺乏类型级别的 lambdas 所困扰。

于 2010-07-01T08:26:49.943 回答