了解一下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'类型不在类定义的范围内.
左和右不是类型,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)
需要注意的是a和b是任意类型.为清楚起见,让我们将a实例重命名为c,将函数重命名f为func.
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是独立的任意类型.
好的,这是另一个非常简单的尝试.
你问为什么这不编译:
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)没有"两个功能的组合"正在进行中.要获取类型签名fmap在Either 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,也很凉爽,但没有必要在所有了解惯用哈斯克尔编程.