之前我问过翻译一元代码以仅使用 Parsec 的应用函子实例。不幸的是,我收到了几个答复,这些答复回答了我真正提出的问题,但并没有真正给我太多的见解。所以让我再试一次......
总结我到目前为止的知识,应用函子比单子更受限制。在“少即是多”的传统中,限制代码可以做的事情增加了疯狂代码操作的可能性。无论如何,很多人似乎认为使用 applicative 代替 monad 是一个可能的更好的解决方案。
该类Applicative
在 中定义Control.Applicative
,其 Haddock 的列表有助于将类方法和实用程序函数与它们之间的大量类实例分开,从而难以同时快速查看屏幕上的所有内容。但是相关的类型签名是
pure :: x -> f x
<*> :: f (x -> y) -> f x -> f y
*> :: f x -> f y -> f y
<* :: f x -> f y -> f x
<$> :: (x -> y) -> f x -> f y
<$ :: x -> f y -> f x
完全有道理,对吧?
嗯,Functor
已经给我们了fmap
,基本上就是<$>
。即,给定一个函数 from x
to y
,我们可以将 an 映射f x
到 an f y
。Applicative
添加了两个本质上新的元素。一个是,它与(以及各种范畴论类中的其他几个运算符)pure
具有大致相同的类型。return
另一个是<*>
,它使我们能够获取一个函数容器和一个输入容器,并产生一个输出容器。
使用上面的运算符,我们可以非常巧妙地做一些事情,比如
foo <$> abc <*> def <*> ghi
这允许我们采用 N 元函数并从 N 个函子中获取其参数,这种方式很容易推广到任何 N。
这点我已经明白了。有两件主要的事情我还不明白。
首先,*>
函数<*
和<$
。从它们的类型来看<* = const
,*> = flip const
、 和<$
可能是相似的。大概这并没有描述这些功能实际上做了什么。(??!)
其次,在编写 Parsec 解析器时,每个可解析实体通常最终看起来像这样:
entity = do
var1 <- parser1
var2 <- parser2
var3 <- parser3
...
return $ foo var1 var2 var3...
由于应用函子不允许我们以这种方式将中间结果绑定到变量,我对如何为最后阶段收集它们感到困惑。为了理解如何做到这一点,我一直无法完全围绕这个想法。