2017-02-06 18 views
1

Существуют функции с подписями вроде:Как цепь а -> IO (м б) функции

a -> IO (m b) 
b -> IO (m c) 
c -> IO (m d) 

Как приковать их в

a -> IO (m d) 

?

Практическое применение: скажем, что существует набор конечных точек REST. Каждому возврату значения и следующему нужно значение, возвращаемое предыдущим в качестве аргумента.

Так функции для выборки из конечных точек, как:

Value1 -> IO (Maybe Value2) 
Value2 -> IO (Maybe Value3) 
Value3 -> IO (Maybe Value4) 
+1

Вы ищете состав Kliesli '(> =>)' Проверьте здесь Http: // hackage .haskell.org/package/base-4.9.1.0/docs/Control-Monad.html # v: -62--61--62- – zeronone

+1

Дополнительно 'IO (возможно, a) ~ MaybeT IO a' и' MaybeT' является определенном в http://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-Maybe.html#v:MaybeT – zeronone

ответ

6

Есть функции с подписями вроде:

a -> IO (m b) 
b -> IO (m c) 
c -> IO (m d) 

Как приковать их в

a -> IO (m d) 

В родах l, вы, возможно, не сможете. Например, если m - Const, я не уверен, что это даже имеет смысл. В общем, вы, вероятно, ожидаете, что m будет Monad. Однако учтите, что даже если mсоставляет a Monad, его состав с IO может не быть (see this).

Value1 -> IO (Maybe Value2) 
Value2 -> IO (Maybe Value3) 
Value3 -> IO (Maybe Value4) 

Ах, теперь вы говорите! Абстракция, которую вы ищете, это MaybeT и состав Kleisli (>=>). Затем, например,

import Control.Monad ((>=>)) 
import Control.Monad.Trans.Maybe (MaybeT(..)) 

rest1 :: Value1 -> IO (Maybe Value2) 
rest2 :: Value2 -> IO (Maybe Value3) 
rest3 :: Value3 -> IO (Maybe Value4) 

rest4 :: Value1 -> IO (Maybe Value4) 
rest4 x = runMaybeT ((MaybeT . rest1 >=> MaybeT . rest2 >=> MaybeT . rest3) x) 

Это все еще выглядит немного уродливым. Возможно, нужно реорганизовать ваши функции rest1, rest2 и rest3. Как было указано в комментариях, MaybeT IO a может быть преобразован в IO (Maybe a) (на самом деле это именно то, что runMaybeT и MaybeT do).

import Control.Monad ((>=>)) 
import Control.Monad.Trans.Maybe (MaybeT(..)) 

rest1 :: Value1 -> MaybeT IO Value2 
rest2 :: Value2 -> MaybeT IO Value3 
rest3 :: Value3 -> MaybeT IO Value4 

rest4 :: Value1 -> MaybeT IO Value4 
rest4 = rest1 >=> rest2 >=> rest3 
+0

После рефакторинга было бы целесообразно реорганизовать функции 'rest' , чтобы вернуть вкус из исходных функций? Например, 'rest1 :: MonadTrans mt => Value1 -> mt IO Value2'? – chepner

+0

@chepner Я бы определенно сказал «да»; хотя я не уверен, что ваш конкретный тип имеет смысл. Что-то вроде 'rest1 :: (MonadIO m, MonadPlus m) => Value1 -> m Value2' будет выражать, что' rest1' может делать некоторые IO ('MonadIO') и может не получиться (' MonadPlus'). –

2

если т имеет экземпляр Traversable (Maybe есть) вот еще одна альтернатива:

import   Control.Monad (join) 

f :: (Traversable m, Monad m) 
    => (a -> IO (m b)) 
    -> (b -> IO (m c)) 
    -> (c -> IO (m d)) 
    -> a 
    -> IO (m d) 
f fa fb fc a = fa a 
      >>= traverse fb 
      >>= fmap join . traverse fc . join 
+0

Похоже, вам не нужны 'liftIO' и' >> = return. join' является 'fmap join'. –

+0

Да, ты прав. –

+0

для части с '>> = return. join' Я хотел отделить последний шаг. поэтому, если бы хотелось добавить в этот пример еще несколько шагов (например, 'd -> IO (m e)'), они просто добавили бы еще одну строку с '>> = traverse fd. join' перед последней строкой. –