重构使用Reader monad的Haskell函数

aru*_*l84 6 monads refactoring haskell do-notation reader-monad

我有一些看起来像这样的代码,忽略了与我的问题无关的所有代码:

import qualified Control.Monad.Reader as Reader

data FooEnv = FooEnv { bar :: Int -> Int }
type FooReader = Reader.Reader FooEnv

foo :: Int -> FooReader String
foo i = Reader.liftM show $ bar' i
  where
    bar' i' = do
      bar'' <- Reader.asks bar
      return $ bar'' i'
Run Code Online (Sandbox Code Playgroud)

有没有办法重构这个?具体来说,嵌套bar'函数最让我困扰.这可以浓缩成一行吗?

J. *_*son 9

我们可以做一点均等推理.首先让我们来看看bar'.我会用这种形式写的

asks bar >>= \z -> return (z i)
Run Code Online (Sandbox Code Playgroud)

事实证明,这liftM被定义为liftM f m = m >>= \a -> return (f a)适合上述模式.所以让我们替换它

liftM ($ i) (asks bar)
Run Code Online (Sandbox Code Playgroud)

然后我们foo就是这样

liftM show (liftM ($ i) (asks bar))
Run Code Online (Sandbox Code Playgroud)

或者,特别写出来

liftM show . liftM ($ i) $ asks bar
Run Code Online (Sandbox Code Playgroud)

如果我们知道这liftMfmap我们可能会认识到Functor这里的法律

fmap show . fmap ($ i) $ asks bar -- equals
fmap (show . ($ i)) $ asks bar
Run Code Online (Sandbox Code Playgroud)

我个人并不是使用($ i)函数的忠实粉丝,所以让我们将它重写为一个显式的lambda

fmap (\f -> show (f i)) (asks bar)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以决定asks通过bar在呼叫站点使用来消除(即bar作为类型的函数使用)bar :: FooEnv -> Int -> Int

fmap (\f -> show (bar f i)) ask
Run Code Online (Sandbox Code Playgroud)

作为最后一招,我们可以使用ped功能flip毫无意义fmap甚至返回使用asks(感谢ØrjanJohansen)

fmap (show . flip bar i) ask  -- or even
show . flip bar i <$> ask     -- or even
asks (show . flip bar i)
Run Code Online (Sandbox Code Playgroud)

我不是说这是执行此任务的最可读或最好的方式,但您可以看到我们如何使用等式推理来减少碎片.

  • 请注意,`问f`相当于`f <$> ask`,所以根据你的喜好你可以在最后重新引入它:`问$ show.翻转栏我` (2认同)