由于许多应用程序也是monad,因此我觉得这个问题确实有两个方面。
当两种接口都可用时,为什么要使用应用接口而不是单子接口?
这主要是风格问题。尽管monad具有-表示法的语法糖,do
但是使用应用程序样式通常会导致代码更紧凑。
在这个例子中,我们有一个类型Foo
,我们想要构造这种类型的随机值。使用monad实例IO
,我们可以编写
data Foo = Foo Int Double
randomFoo = do
x <- randomIO
y <- randomIO
return $ Foo x y
适用的变体要短得多。
randomFoo = Foo <$> randomIO <*> randomIO
当然,我们可以使用它liftM2
来获得类似的简洁性,但是应用样式比必须依赖于特定于Arity的提升功能更为简洁。
在实践中,我通常会以与使用无点样式相同的方式使用应用程序:当一个操作更清楚地表示为其他操作的组合时,避免命名中间值。
我为什么要使用非monad的应用程序?
由于应用程序比monad受更严格的限制,因此这意味着您可以提取关于它们的更多有用的静态信息。
应用解析器就是一个例子。monadic解析器支持顺序组合使用(>>=) :: Monad m => m a -> (a -> m b) -> m b
,而应用解析器仅使用(<*>) :: Applicative f => f (a -> b) -> f a -> f b
。类型使区别显而易见:在单子语法分析器中,语法可以根据输入而变化,而在应用语法分析器中,语法是固定的。
通过以这种方式限制接口,我们可以例如确定解析器是否不运行就接受空字符串。我们还可以确定第一个和第二个集合,这些集合可以用于优化,或者正如我最近一直在玩的那样,构造支持更好错误恢复的解析器。