为什么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中的等价物可以互换,在功能上完全没有区别。 考虑例如pure
和return
编辑:我写了一些例子,但它们真的很糟糕,因为它们涉及了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
前者是“不太通用的”,因为它使用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
结构的长度。 另一方面,我绝对不想意外地考虑一对长度,或者采用我认为具有类型[a]
的foo bar baz
的长度,但实际上具有类型Maybe [a]
。
在其他Haskell标准尚未涵盖的Foldable用例中,镜头是一种更为强大的替代方案。 如果我想要一个Maybe t
的“length”, lengthOf _Just :: Maybe t -> Int
可以清楚地表达我的意图,并且编译器可以检查程序是否真的符合我的意图; 并且我可以继续写lengthOf _Nothing
, lengthOf _Left
等。显式比隐式更好。
上一篇: Why does Haskell contain so many equivalent functions
下一篇: Haskell threads heap overflow despite only 22Mb total memory usage?