由于许多applicative也是monad,我觉得这个问题实际上有两个方面。
既然两者都可用,为什么要使用applicative接口而不是monadic接口?
这主要取决于编程风格。虽然monad具有do
-notation的语法糖,但使用applicative风格通常会导致更紧凑的代码。
在这个例子中,我们有一个类型Foo
,并且我们想构造这个类型的随机值。使用IO
的monad实例,我们可能会写成:
data Foo = Foo Int Double
randomFoo = do
x <- randomIO
y <- randomIO
return $ Foo x y
应用变量更加简短。
randomFoo = Foo <$> randomIO <*> randomIO
当然,我们可以使用
liftM2
来获得类似的简洁性,但是applicative样式比依赖于特定元数的lifting函数更整洁。
在实践中,我发现自己主要使用applicatives的方式与我使用point-free样式的方式非常相似:当一个操作更清晰地表示为其他操作的组合时,避免命名中间值。
为什么我想使用不是monad的applicative?
由于applicatives比monads更受限制,这意味着您可以提取有关它们的更多有用的静态信息。
其中一个例子是applicative解析器。而monadic解析器支持使用
(>>=) :: Monad m => m a -> (a -> m b) -> m b
进行顺序组合,applicative解析器仅使用
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
。类型使区别显而易见:在monadic解析器中,语法可以根据输入而改变,而在applicative解析器中,语法是固定的。
通过以这种方式限制接口,例如,我们可以确定解析器是否接受空字符串
without running it。我们还可以确定第一个和跟随集合,这可用于优化,或者如我最近所尝试的那样,构建支持更好的错误恢复的解析器。
Applicative
是迭代器模式的本质。 - Russell O'Connor