在Haskell中用另一个函数作为参数提升函数

时间:2014-11-01 19:01:45

标签: function haskell collections monads lifting

所以我在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

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

1 个答案:

答案 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