让Haskell函子沉入其中.

Eva*_*oll 31 haskell functor

了解一下Haskell有一个关于仿函数的例子.我可以阅读LYAH和文本,并弄清楚应该发生什么 - 但我不知道写这样的东西.我经常在Haskell中发现这个问题.

instance Functor (Either a) where  
    fmap f (Right x) = Right (f x)  
    fmap f (Left x) = Left x
Run Code Online (Sandbox Code Playgroud)

但是,我很困惑..为什么不这个补充

instance Functor (Either a) where  
    fmap f (Right x) = Right (x)  
    fmap f (Left x) = Left (f x)
Run Code Online (Sandbox Code Playgroud)

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

Joh*_*n L 41

这是仿函数类:

class Functor f where
  fmap :: (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

请注意,"f"本身是一个类型构造函数,因为它应用于fmap行中的类型变量.以下是一些清楚说明的例子:

类型构造函数:

IO
Maybe
Either String
Run Code Online (Sandbox Code Playgroud)

类型:

IO Char
Maybe a
Either String String
Run Code Online (Sandbox Code Playgroud)

"也许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
Run Code Online (Sandbox Code Playgroud)

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

用"Either c"替换"f"以使用"Either c"实例获得fmap的上述类型签名后,接下来编写实现.有两种情况需要考虑,左派和右派.类型签名告诉我们左侧的类型"c"不能改变.我们也没有办法改变价值,因为我们不知道它实际上是什么类型.我们所能做的就是不管它:

fmap f (Left rval) = Left rval
Run Code Online (Sandbox Code Playgroud)

对于右侧,类型签名表示我们必须从类型为"a"的值更改为类型为"b"的值.第一个参数是一个完全相同的函数,因此我们使用带有输入值的函数来获取新的输出.将两者放在一起给出了完整的定义

instance Functor (Either c) where
  fmap f (Right rval) = Right (f rval)
  fmap f (Left lval) = Left lval
Run Code Online (Sandbox Code Playgroud)

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

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

instance Functor (Either c) where ...
Run Code Online (Sandbox Code Playgroud)

即使我们在实例定义中有一个类型变量'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
Run Code Online (Sandbox Code Playgroud)

现在是leftMap和fmap的结果(Either d) a.在(Either c)已更改为一个(Either d),但这是不允许的,因为没有办法表达出来的仿函数类.为了表达这一点,你需要一个有两个类型变量的类,例如

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
Run Code Online (Sandbox Code Playgroud)

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

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

虽然在实践中人们通常只为BiFunctor类编写"biMap",如果需要左或右映射,则使用"id"作为其他函数.

[1]更准确地说,Left值的类型为'c',函数需要'a',但是类型检查器不能统一这些类型,因为'c'类型不在类定义的范围内.


ken*_*ytm 9

左和右不是类型,Left x并且Right y属于同一类型.他们只是Either的构造者.你可以考虑一下

Left :: c -> Either c d
Right :: d -> Either c d
Run Code Online (Sandbox Code Playgroud)

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

g :: Int -> Int
g 1 = 2
g 2 = 4
g n = n
Run Code Online (Sandbox Code Playgroud)

这里我们不能说1和2并且n只是因为模式匹配起作用而是不同的"类型".


Functor类定义为

class Functor f where
  fmap :: (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

需要注意的是ab是任意类型.为清楚起见,让我们将a实例重命名为c,将函数重命名ffunc.

instance Functor (Either c) where  
    fmap func (Right x) = Right (x)  
    fmap func (Left x) = Left (func x)
Run Code Online (Sandbox Code Playgroud)

假设您的Either遵循默认定义

data Either c d = Left c | Right d
Run Code Online (Sandbox Code Playgroud)

然后根据你的定义,

fmap    func     (Right x) = Right x
-- # (a -> b) ->      f a       f  b
-- # f = Either c
Run Code Online (Sandbox Code Playgroud)

这种力量a = b,和

fmap    func     (Left x) = Left (func x)
-- # (a -> b) ->     f a       f b
-- # f = Either c
Run Code Online (Sandbox Code Playgroud)

部队c = a = b.两者都无效a,b并且c是独立的任意类型.


scl*_*clv 8

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

你问为什么这不编译:

instance Functor (Either a) where
    fmap f (Right x) = Right (x)
    fmap f (Left x) = Left (f x)
Run Code Online (Sandbox Code Playgroud)

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

这给了我们

foo f (Right x) = Right (x)
foo f (Left x) = Left (f x)
Run Code Online (Sandbox Code Playgroud)

确实编译.ghci告诉我们类型签名:

*Main> :t foo
foo :: (t1 -> a) -> Either t1 t -> Either a t
Run Code Online (Sandbox Code Playgroud)

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

foo :: (a -> b) -> Either a c -> Either b c
Run Code Online (Sandbox Code Playgroud)

这很有道理.它需要一个函数并将其应用于Either的左侧.

但是fmap的签名是什么?

*Main> :t fmap
fmap :: (Functor f) => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

因此,让我们代替Either c了的F fmap签名(我改名Either a,以Either c保持我们的两个不同a期从混错):

fmap :: (a -> b) -> Either c a -> Either c b
Run Code Online (Sandbox Code Playgroud)

你看到了问题吗?您的功能完全有效 - 它只是具有与fmap Either a 必须具有的不同类型.

关于类型,这是一种美妙的事情.鉴于签名fmap,在A或A上实际上只有一个有意义的实现fmap.

有时,当我们幸运和谨慎时,我们可能会遇到类似的情况 - 给定一个类型签名,该函数几乎会自行编写.

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

1)没有"两个功能的组合"正在进行中.要获取类型签名fmapEither a刚刚经历的fmap函数签名,每次你看到的地方f,将其替换为Either a.我们称之为fmap类型签名的"特化".也就是说,它严格地不如普通类型的fmap - 任何需要更专业类型的函数的地方,你可以传递一般类型的东西没有问题.

2)你在左侧绘图的功能(我在上面的例子中命名为"foo")就好了.它工作正常,它做你想要的.您无法命名它fmap并在Functor实例中使用它.就个人而言,我将其命名为onLeft或者mapLeft.

以下所有内容均可忽略/供参考,但不建议将来阅读/实际使用:

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

有许多很酷的东西涉及计算程序,以及"折纸编程"更严格地使用bifunctors.你可以在这里阅读:http://lambda-the-ultimate.org/node/1360.但是,你可能不想,至少在你对Haskell更熟悉之前.它是计算机sciency,mathy,researchy,也很凉爽,但没有必要在所有了解惯用哈斯克尔编程.