Поднятие функции с другой функцией в качестве аргумента в Haskell

68
9

Поэтому у меня есть функция в 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 с сигнатурой, подобной:

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

Я пробовал так много всего, но я просто не могу реализовать эту функцию! Может ли кто-нибудь помочь?

спросил(а) 2014-11-01T22:01:00+03:00 5 лет, 8 месяцев назад
1
Решение
86

В принципе, вы хотите создать Monad m => ma → mb → mc из a → b → c. То, что liftM2 (из Control.Monad):

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

Продвигайте функцию в монаду, просматривая монадические аргументы слева направо. Например,

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.

ответил(а) 2014-11-01T23:01:00+03:00 5 лет, 8 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема