2

所以我在 Haskell 中有一个函数,为了问这个问题,我已经简化了它:

import Data.Foldable
import Data.Set

myFn :: Int -> Set Int
myFn a
  | a <= 0 = singleton 1
  | otherwise = foldMap helper (myFn (a - 1))

helper :: Int -> Set Int
helper a = insert (a + 2) (singleton a)

main :: IO ()
main = print . Data.Set.toList $ myFn 5

我希望将myFn' 的依赖项helper放入 aReader中,因为控制反转允许我在测试中切换实现:

import Control.Monad.Reader
import Data.Foldable
import Data.Set

data MyEnv = MyEnv { helper' :: Int -> Set Int }
type MyReader = Reader MyEnv

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0 = return $ singleton 1
  | otherwise = do
      myFn' <- myFn (a - 1)
      helper'' <- asks helper'
      return (foldMap helper'' myFn')

helper :: Int -> Set Int
helper a = insert (a + 2) (singleton a)

main :: IO ()
main =
  let
    myEnv = MyEnv helper
  in
    print . Data.Set.toList $ runReader (myFn 5) myEnv

这很好用,除了我特别不喜欢这三行:

myFn' <- myFn (a - 1)
helper'' <- asks helper'
return (foldMap helper'' myFn')

我觉得应该有一种方法来提升foldMap,就像通过它的组合mapM的提升版本一样。理想情况下,我希望将这三行合并为一行:mapsequence

foldMapM helper'' (partitions (n - 1))

假如说:helper'' :: Int -> MyReader (Set Int)

这当然需要具有foldMapM类似于以下签名的函数:

foldMapM
  :: (Monad m, Foldable t, Monoid n)
  => (a -> m n)
  -> m (t a)
  -> m n

我已经尝试了很多东西,但我似乎无法实现这个功能!任何人都可以帮忙吗?

4

1 回答 1

4

基本上,您想Monad m => m a -> m b -> m ca -> b -> c. 这正是liftM2(from Control.Monad) 所做的:

liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r

将函数提升为 monad,从左到右扫描 monad 参数。例如,

liftM2 (+) [0,1] [0,2] = [0,2,1,3]
liftM2 (+) (Just 1) Nothing = Nothing

因此,它就像使用一样简单liftM2 foldMap

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0    = return $ singleton 1
  | otherwise = liftM2 foldMap (asks helper') (myFn (a - 1))

或者,如果您不喜欢额外的括号,您可以使用<$>and <*>from :Control.Applicative

myFn :: Int -> MyReader (Set Int)
myFn a
  | a <= 0    = return $ singleton 1
  | otherwise = foldMap <$> asks helper' <*> myFn (a - 1)

有关更多信息,请查看Typeclassopedia

于 2014-11-01T20:01:53.877 回答