为什么Haskell包含如此多的等价函数

似乎有很多功能可以做同样的事情,特别是Monad,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组合),这种类型的东西根本不是初学者友好的,因为简单地使用这些类型并不那么困难,推论他们的财产和法律需要更多的精力。

我猜想很多这些等价物都来自适用Monad方案。 如果这是他们的原因(而不是其他原因,我可能会因混淆而导致泛型功能较少),他们是否会被弃用/删除? 由于破坏了现有的代码,我可以理解等待很长时间才能删除它们,但肯定会弃用这个好主意?


简短的答案是“历史”和“规律”。

最初的“地图”是为列表定义的。 然后引入类型类和Functor类型类,所以任何函子的“map”的通用版本必须被称为不同的东西,否则现有的代码将被破坏。 因此“fmap”。

然后monad来了。 monads的实例不需要是函数,所以创建“liftM”,以及“liftM2”,“liftM3”等等。当然,如果一个类型是Monad和Functor的一个实例,那么fmap = liftM。

Monads也有“ap”,用于像f `ap` arg1 `ap` arg2这样的表达式。 这非常方便,但添加了Applicative Functor。 (*)对应用函数做了与'ap'相同的工作,但由于许多应用函数不是monad,所以它不得不被称为不同的东西。 同样liftAx与liftMx和“纯”与“回归”。


尽管如此,它们并不相同。 在haskell中的等价物可以互换,在功能上完全没有区别。 考虑例如purereturn

编辑:我写了一些例子,但它们真的很糟糕,因为它们涉及了Maybe a ,一种既是应用又是monad的类型,所以这些函数可以相当互换使用。

有些类型是应用程序,但不是monad(尽管参见这个问题的例子),并且通过研究以下表达式的类型,我们可以看到这可能导致一些路障:

pure 1 >>= pure :: (Monad m, Num b) => m b

我个人觉得这很烦人,因为它意味着如果我需要做x,并且像liftM这样的函数允许我做x,那么我会让我的函数比它的可能性更少

这个逻辑是倒退的。

通常你事先知道你要写的东西的类型,无论是IO String还是(Foldable f, Monoid t, Monad m) => f (mt) -> mt或其他。 我们来看第一个例子getLineCapitalized :: IO String 。 你可以把它写成

getLineCapitalized = liftM (map toUpper) getLine

要么

getLineCapitalized = fmap (fmap toUpper) getLine

前者是“不太通用的”,因为它使用liftMmap的专门功能? 当然不是。 这本质上是一个产生列表的IO操作。 因为那些它不能成为其更改为第二版“更通用的” fmap旨意他们的类型固定IO[]反正。 所以,第二个版本没有优势。

通过编写第一个版本,您可以免费向读者提供上下文信息。 在liftM (map foo) bar ,读者知道该bar将成为某个monad中的一个操作,返回一个列表。 在fmap (fmap foo) bar ,它可以是任何类型的双重嵌套结构。 如果bar是复杂的而不是getLine ,那么这种信息有助于更容易地理解bar发生的事情。

一般来说,你应该分两步写一个函数。

  • 确定函数的类型应该是什么。 根据需要将其作为一般或特定的。 函数的类型越一般,从参数性上可以得到更强的保证。

  • 一旦你决定了你的功能的类型,使用最具体的可用功能来实现它。 通过这样做,您可以向您的功能的读者提供最多的信息。 这样做不会失去通用性或参数保证,因为这些只取决于您在步骤1中已经确定的类型。


  • 编辑回应评论:我被提醒了使用最具体的功能的最大原因,这是捕捉错误 。 类型length :: [a] -> Int基本上是我仍然使用GHC 7.8的全部原因。 从来没有发生过我想要采用未知Foldable结构的长度。 另一方面,我绝对不想意外地考虑一对长度,或者采用我认为具有类型[a]foo bar baz的长度,但实际上具有类型Maybe [a]

    在其他Haskell标准尚未涵盖的Foldable用例中,镜头是一种更为强大的替代方案。 如果我想要一个Maybe t的“length”, lengthOf _Just :: Maybe t -> Int可以清楚地表达我的意图,并且编译器可以检查程序是否真的符合我的意图; 并且我可以继续写lengthOf _NothinglengthOf _Left等。显式比隐式更好。

    链接地址: http://www.djcxy.com/p/51753.html

    上一篇: Why does Haskell contain so many equivalent functions

    下一篇: Haskell threads heap overflow despite only 22Mb total memory usage?