2012-02-15 1 views
0

Скажем, у меня есть вычислениеКаков наилучший способ поддержки монадных трансформаторов в функциях, которые принимают монадические команды в качестве аргументов?

class A where 
    foo :: () -> () 

instance A IO where 
    foo x = do 
     print "prefix" 
     x 
     print "suffix" 

Теперь предположим, что я хочу написать

instance A => A (MyMonadTransformerT γ) 

Затем в реализации foo, я вынужден «разворачивать» свой аргумент, например foo x = lift (foo (unlift x)). Эта функция unlift может быть плохой для монадических вычислений. Для трансформатора состояния он будет вынужден забыть о любых изменениях в состоянии программы.

Как представляется, для создания более общего метода, который также выполняет функцию подъема, и приводит к вычислению t() -> t(), где t является поднятой (траншейной) монадой.

class Monad => A' where 
    foo' :: Monad t => 
     (forall z . z -> t z) -- lifting function 
     -> t() 
     -> t() 
    foo :: () -> () 
    foo = foo' id 

instance A' IO where 
    foo' lift x = do 
     lift (print "prefix") 
     x 
     lift (print "suffix") 

instance A' => A' (StateT γ) where 
    foo' lift' x = foo' (lift' . lift) x 

computation :: Num a => StateT a IO() 
computation = do 
    foo (put 1 >> lift (print "middle")) 
    v <- get 
    lift $ print ("value", v) 

run_computation :: Num a => IO a 
run_computation = execStateT computation 0 

Вопрос. Это лучший способ? Есть ли что-то более общее, что можно написать? Код в стиле CPS? Благодаря!!

+2

Возможный дубликат [Подъем функции более высокого порядка в Haskell] (http://stackoverflow.com/questions/9243215/lifting-a-higher-order-function-in-haskell) –

+0

Да, я думаю, что это дубликат , Подход «refying the class» I и luqui взял, кажется, самый приятный. – gatoatigrado

ответ

2

Прежде всего, забудьте, что class бизнес, похоже, вы просто хотите функцию.

Эта проблема решается с помощью Monad* классов: MonadIO, MonadState и т.д. Так что, если у вас есть монадическое вычисление, которое может сделать IO, но разрешено делать другие вещи, вы бы в качестве параметра типа m любой монады которые могут выполнять действия ввода-вывода:

foo :: (MonadIO m) => m() -> m() 
foo x = do 
    liftIO $ putStrLn "prefix" 
    x 
    liftIO $ putStrLn "suffix" 

Теперь это не имеет значения, что m есть, потому что MonadIO говорит, как поднять его обратно к операции, которые вы хотите.

Классы Monad* несколько немодулированы перед новыми трансформаторами - количество экземпляров, которые вам нужны, квадратично по числу монадных трансформаторов. Существуют различные субоптимальные решения этой проблемы. Если такие вещи касаются вас, вы всегда можете материализовать класс:

foo :: (Monad m) => (forall a. IO a -> m a) -> m() -> m() 
foo lift x = do 
    lift $ putStrLn "prefix" 
    x 
    lift $ putStrLn "suffix" 

ли это сделать, зависит от вашего уровня абстракции. Вам понадобится первая, если вы пишете библиотеку, на которой будет создан код контента, и, возможно, последний, если вы пишете библиотеку, на которой будет создан другой библиотечный код. Это все-таки сложно, но все потому, что стеки монады не коммутируют.

+0

Я не показывал другие экземпляры класса. Итак, да, я уверен, я хочу класс, но только для 'foo'',' foo' должен быть функцией. Кроме того, компилятор не сможет сделать вывод о том, следует ли использовать «лифт» (1 монодатный трансформатор), «лифт». лифт (2 монадных трансформатора) и т. д. сам по себе. – gatoatigrado

Смежные вопросы