似乎有很多功能可以做同样的事情,特别是与Monads,Functors和Applicatives有关。
示例(从大多数到最不通用):
fmap == liftA == liftM
(<*>) == ap
liftA[2345] == liftM[2345]
pure == return
(*>) == (>>)
不直接基于FAM类树的示例:
fmap == map
(我认为List,Foldable,Traversable还有很多,但看起来大多数都是前段时间更通用的,因为我只看到旧堆栈溢出/留言板中旧的,不太通用的类型签名题)
我个人认为这很烦人,因为这意味着如果我需要做x,而某些函数如liftM允许我做x,那么我会使我的函数不那么通用,而且我是只是通过彻底推断类型之间的差异(例如FAM,或者可能是List,Foldable,Traversable组合)来注意那种事情,这对于初学者来说并不友好,因为只是简单地使用这些类型而不是#39所有这些都很难,对其属性和法律的推理需要更多的心理努力。
我猜这些等价物来自申请Monad提案。如果这就是他们的原因(并且由于可用于混淆的通用功能较少而缺少其他原因),他们是否会被弃用/删除?由于破坏现有代码,我可以理解等待很长时间才能删除它们,但肯定会弃用是个好主意吗?
答案 0 :(得分:5)
简短的答案是“历史”和“规律性”。
最初为地图定义了“地图”。然后引入类型类,使用Functor类型类,因此任何仿函数的“map”的通用版本必须被称为不同的东西,否则现有代码将被破坏。因此“fmap”。
然后monad出现了。 monads的实例不需要是仿函数,因此创建了“liftM”,以及“liftM2”,“liftM3”等。当然,如果类型是Monad和Functor的实例,那么fmap = liftM。
Monads也有“ap”,用于像f `ap` arg1 `ap` arg2
这样的表达式。这非常方便,但随后添加了Applicative Functors。 (&lt; *&gt;)和'ap'一样对应用函子执行相同的工作,但是因为许多应用函子不是monad所以它必须被称为不同的东西。同样,liftAx与liftMx相比,“pure”与“return”相同。
答案 1 :(得分:1)
但它们并不相同。 haskell中的等价物可以互换,功能上没有任何区别。例如,请考虑pure
和return
Maybe a
,这是一个既适用又适合monad的类型,所以函数可以互换使用。
虽然有些类型是应用但不是monad(请参阅this问题示例),通过研究以下表达式的类型,我们可以看到这可能导致一些道路颠簸:
pure 1 >>= pure :: (Monad m, Num b) => m b
答案 2 :(得分:0)
我个人认为这很烦人,因为这意味着如果我需要做x,而某些函数如liftM允许我做x,那么我将使我的函数不那么通用
这种逻辑是倒退的。
通常,您事先知道要写的东西的类型,无论是IO String
还是(Foldable f, Monoid t, Monad m) => f (m t) -> m t
还是其他什么。我们来看第一个案例getLineCapitalized :: IO String
。你可以把它写成
getLineCapitalized = liftM (map toUpper) getLine
或
getLineCapitalized = fmap (fmap toUpper) getLine
前者是否“不太通用”,因为它使用专门的函数liftM
和map
?当然不是。这本质上是一个生成列表的IO操作。通过将其更改为第二个版本,它不会变得“更通用”,因为那些fmap
的类型无论如何都会将其类型固定为IO
和[]
。所以,第二版没有任何优势。
通过编写第一个版本,您可以免费向读者提供上下文信息。在liftM (map foo) bar
中,读者知道bar
将成为返回列表的某个monad中的操作。在fmap (fmap foo) bar
中,它可以是任何类型的双嵌套结构。如果bar
复杂而不仅仅是getLine
,那么此类信息有助于更轻松地了解bar
中发生的情况。
通常,您应该分两步编写一个函数。
确定函数的类型应该是什么。根据需要将其设为一般或特定。函数的类型越一般,就越能保证你从参数化中获得它的行为。
确定函数类型后,使用最具体的可用函数实现它。通过这样做,您可以向函数的读者提供最多信息。通过这样做,您永远不会失去任何一般性或参数保证,因为这些仅取决于您在步骤1中已确定的类型。
编辑以回应评论:我被提醒使用最具体的功能的最大原因,即捕获错误。类型length :: [a] -> Int
基本上是我仍然使用GHC 7.8的全部原因。我从未想过采用未知Foldable
结构的长度。另一方面,我绝对不希望意外地占用一对的长度,或者取foo bar baz
的长度,我认为其类型为[a]
,但实际上有Maybe [a]
类型
在可折叠的用例中,Haskell标准的其余部分尚未涵盖,镜头是一种更强大的替代品。如果我想要Maybe t
的“长度”,lengthOf _Just :: Maybe t -> Int
清楚表达我的意图,编译器可以检查程序是否真正符合我的意图;我可以继续写lengthOf _Nothing
,lengthOf _Left
等。明确比隐含更好。
答案 3 :(得分:0)
有一些“冗余”功能,例如liftM
,ap
和liftA
,它们具有非常实际的用途并将其取出会导致功能丧失 - 您可以使用liftM
,ap
和liftA
实施Functor
或Applicative
个实例,如果你所写的只是Monad
个实例。它让你变得懒惰,并说:
instance Monad Foo where
return = ...
(>>=) = ...
现在你已经完成了定义Monad
实例的所有有价值的工作,但这不会编译。为什么?因为您还需要Functor
和Applicative
实例。
因此,由于您正在快速进行原型设计,或者懒惰,或者无法想出更好的方法,因此您可以获得免费的Functor
和Applicative
实例:
instance Functor Foo where
fmap = liftM
instance Applicative Foo where
pure = return
(<*>) = ap
实际上,只要您已经定义了Functor
实例,就可以在需要的任何地方复制并粘贴该块代码,以快速定义Applicative
或Monad
实例。
来自fmapDefault
的{{1}}同样如此。如果您已实施Data.Traversable
,则还可以实施Traversable
和Foldable
:
Functor
无需额外工作!
然而,有一些冗余函数除了instance Functor Bar where
fmap = fmapDefault
不是Functor
的超类之外的历史事故时,实际上没有实际用法。这些在现有内容中实际上没有使用/点数......包括Monad
,liftM2
等内容,以及liftM3
和朋友。