如何将 fclabels 镜头提升到 Monad?

How can I lift an fclabels Lens to a Monad?

我正在尝试使用 fclabels, from v0.4 to v2.0 (lts-3.17) 更新一些旧代码,将 label/lens 提升为 monad。旧代码是:

{-# LANGUAGE TypeOperators #-}
import Data.Record.Label
import Control.Monad (liftM, liftM2)

liftMLabel :: Monad m => a :-> b -> m a :-> m b
liftMLabel l = label (liftM $ get l) (liftM2 $ set l)

所以我做的第一件事就是将 label 更改为 lens,将 set 更改为 modify:

{-# LANGUAGE TypeOperators #-}
import Data.Label
import Control.Monad (liftM, liftM2)

liftMLabel :: Monad m => a :-> b -> m a :-> m b
liftMLens l = lens (liftM $ get l) (liftM2 $ modify l)

这给出了以下编译器错误:

Expected type: (m b -> m b) -> m a -> m a
  Actual type: m (b -> b) -> m a -> m a

这是有道理的,给出 liftM2 如何处理 lens modify 函数的每个参数。

旧的 fclabels 使用 setter 函数来创建标签,它接受一个简单的值参数。 modify 函数,用于创建更新的 fclabels 镜头,接受一个使用现有值进行修改的函数,我明白了为什么它也会对单子参数进行操作。

我需要为修改函数执行一些额外的管道,我看到 ap 做的事情与我想要的类似,但我不清楚总体上最好的方法是什么。

那么有什么好的方法来处理modify函数,让我匹配到预期的类型呢?

与其试图用无意义的方式一次写完,为什么不直接用手写的方式写呢?

liftMLens :: (Monad m) => a :-> b -> m a :-> m b
liftMLens l = lens (liftM $ get l) $ \f mx -> do
    x <- mx
    let v = get l x
    v' <- f $ return v
    return $ set l v' x

当然,如果你真的想,你可以用代码打高尔夫球...