所以我在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
放入Reader
,因为控制反转允许我在我的测试中切换实现:
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
通过map
的构图sequence
的提升版本一样。理想情况下,我希望这三行折叠成一行:
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
我尝试了很多东西,但我似乎无法实现这个功能!有人可以帮忙吗?
答案 0 :(得分:4)
基本上,您希望从Monad m => m a -> m b -> m c
创建a -> b -> c
。这正是liftM2
(来自Control.Monad
)的作用:
liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
向Monad提升函数,扫描monadic参数 左到右。例如,
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))
或者,如果您不喜欢其他括号,则可以使用<$>
中的<*>
和Control.Applicative
:
myFn :: Int -> MyReader (Set Int)
myFn a
| a <= 0 = return $ singleton 1
| otherwise = foldMap <$> asks helper' <*> myFn (a - 1)
有关详细信息,请查看Typeclassopedia。