重构使用Reader monad的Haskell函数

时间:2014-09-08 04:06:59

标签: haskell refactoring monads 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'

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

1 个答案:

答案 0 :(得分:9)

我们可以做一些基本的推理。首先让我们看一下bar'。我会用这种形式写出来

asks bar >>= \z -> return (z i)

事实证明,liftM被定义为符合上述模式的liftM f m = m >>= \a -> return (f a)。所以让我们用

替换它
liftM ($ i) (asks bar)

然后我们将foo作为

liftM show (liftM ($ i) (asks bar))

或者,写得有点特别

liftM show . liftM ($ i) $ asks bar

如果我们知道liftMfmap,我们可能会认识到此处的Functor法律

fmap show . fmap ($ i) $ asks bar -- equals
fmap (show . ($ i)) $ asks bar

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

fmap (\f -> show (f i)) (asks bar)

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

fmap (\f -> show (bar f i)) ask

作为最后一招,我们可以使用flipfmap ped函数中毫无意义,甚至返回使用asks(感谢ØrjanJohansen)

fmap (show . flip bar i) ask  -- or even
show . flip bar i <$> ask     -- or even
asks (show . flip bar i)

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