写 parser 的时候需要写这样的代码: .. code-block:: haskell takeTill pred 其中 `pred` 是个判别函数,签名为: `Char -> Bool` 。 `takeTill` 的作用就是一直解析到 `pred` 返回 `True` 为止。 比如你要解析到下一个 `>` 或者 `=` ,你就写: .. code-block:: haskell takeTill (inClass ">=") 想解析到下一个空白字符为止,就写: .. code-block:: haskell takeTill isSpace 想解析各种空白字符呢,就在上面的基础上取个反: .. code-block:: haskell takeTill (not . isSpace) 这个例子是想提醒大家 `isSpace` 是个函数,所以这里需要进行函数组合,而不是直接调用 `not` 。 那我今天想说的是什么呢。现在我想解析到下一个 `>` 或 `=` 或空白字符为止,也就是说需要把前两个拼起来,直接写起来是这样的: .. code-block:: haskell takeTill (\c -> inClass ">=" c || isSpace c) 也不麻烦,只不过对于患有 `代码洁癖 `_ 的我来说,视觉上还不太给力。于是,我继续重构如下: .. code-block:: haskell takeTill (inClass ">=" ||. isSpace) 多实现一个组合函数 `||.` : .. code-block:: haskell (||.) :: (a -> Bool) -> (a -> Bool) -> a -> Bool (||.) f g a = f a || g a 把两个判别函数组合成一个新的判别函数。 为了将它推广到其他的组合操作,我们进一步泛化一个通用函数,姑且取名叫 `fn` 吧 : .. code-block:: haskell fn :: (b -> b -> b) -> (a -> b) -> (a -> b) -> a -> b fn op f g a = f a `op` g a 前面的 `||.` 就成为: .. code-block:: haskell (||.) = fn (||) 大功告成,正当我准备好好欣赏一下最终的产物 `fn` 的时候,却突然发现, `fn` 的作用不就是把 `b` 层面的二元函数提升到 `(a -> b)` 层面么,正如 `||` 和 `||.` 都是或操作,只不过一个作用在 `Bool` 值层面,一个作用在 `a -> Bool` 判别函数层面。如此通用的概念,我意识到我很可能重造轮子了。 于是我请 `lambdabot `_ mm帮我诊断一下: .. code-block:: haskell @pl fn op g k a = g a `op` k a fn = liftM2 果然,小mm告诉我, `fn` 其实就是 `liftM2` 。 `liftM2` 是专门用来把二元函数提升到 `Monad` 中去的,而 `((->) a)` 正是 `Monad` 的实例。 .. code-block:: haskell instance Monad ((->) a) where return = const -- (>>=) :: (a -> b) -> (b -> a -> c) -> (a -> c) f >>= g = g . f liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c liftM2 op ma mb = do a <- ma b <- mb return (a `op` b) 其实只需要提升一下抽象层次的话,还不需动用 `Monad` 这样的大杀器, `Applicative` 也可以搞定。 .. code-block:: haskell instance Applicative ((->) a) where -- <$> :: (b -> c) -> (a -> b) -> (a -> c) f <$> g = f . g -- (<*>) :: (a -> b -> c) -> (a -> b) -> (a -> c) f <*> g = \a -> (f a) (g a) 用 `Applicative` 的话,前面的 `fn` 就等价于 `liftA2` 了。 .. code-block:: haskell liftA2 :: Application f => (a -> b -> c) -> f a -> f b -> f c liftA2 op fa fb = op <$> fa <*> fb 绕了一圈,最后还是没逃出Haskell最基本的框框。