2011-10-07 13 views
14

Tengo el siguiente código pero creo que es demasiado feo e imperativo. ¿Alguien lo reformularía para ser más funcional? (Me metí con MaybeT pero no pude hacerlo funcionar) Las respuestas aplicables también son bienvenidas.IO y quizás Interacción de mónada

getString :: IO String 

pred :: String -> Bool 

f :: String -> String 

result :: IO (Maybe String) 
result = do 
    s <- getString 
    if pred s 
    then return $ Just $ f s 
    else return Nothing 

EDIT: Una pregunta de seguimiento: ¿y si ambos de Pred y F también proporcione resultados dentro de IO (debo dividir esto en una pregunta separada?)

getString :: IO String 

pred :: String -> IO Bool 

f :: String -> IO String 

result :: IO (Maybe String) 
result = do 
    s <- getString 
    b <- pred s 
    if b 
    then Just <$> f s 
    else return Nothing 

Respuesta

25

lo haría Comience tomando la lógica aquí fuera de la mónada IO. Su función puede ser escrita como

result :: IO (Maybe String) 
result = foo <$> getString 

foo :: String -> Maybe String 
foo s | pred s = Just (f s) 
     | otherwise = Nothing 

Probablemente se podría escribir foo de diferentes maneras usando algunos combinadores de lujo, pero no creo que eso sea necesario aquí. Lo más importante es sacar su lógica de IO para que sea más fácil de probar.

+6

+1 No se puede negar que obtener la lógica de 'IO' es más importante que tener una solución elegante de una sola línea. – leftaroundabout

5
import Control.Monad 

result = getString >>= (return . fmap f . (mfilter pred . Just)) 
+0

Observe que 'mfilter' reemplaza su if-then-else. – fuz

+2

'resultado = (f <$> mfilter pred.Solo) <$> getString' – fuz

+0

@FUZxxl que no escribe check. 'mfilter pred. Just :: a -> Maybe a', pero 'f <$> x' espera' x :: Maybe a'. Necesita algún tipo de operador de composición aplicativa 'f <.> g = \ x -> f <$> g x', que si tiene una prioridad menor que' .' permite 'result = (f <.> mfilter pred. Just) <$> getString'. Además, reemplazar 'Just' con' return' permite utilizar cualquier 'Monad'. – pat

3

No se puede llegar fácilmente con el torpe if-then-else, pero puede salirse con la redundante returns:

import Control.Monad 

result :: IO (Maybe String) 
result = go <$> getString where 
    go s | pred s = Just $ f s 
     | otherwise = Nothing 
+0

http://blog.n-sch.de/2010/11/27/rebindable-if-then-else-expressions/ –

9

La transformación obvio a partir de su código es factorizar las operaciones return:

result = do 
    s <- getString 
    return $ if pred s 
      then Just (f s) 
      else Nothing 

Esto hace que el patrón más aparente:

result = liftM g getString 
g s | pred s = Just (f s) 
    | otherwise = Nothing 

Aplicando f desde fuera, podemos hacer el siguiente patrón aparente:

g s = liftM f $ if pred s then Just s else Nothing 

que nos permite REMPLACE el bloque if:

g = liftM f . mfilter pred . return 

resumirlo:

result = liftM (liftM f . mfilter pred . return) getString 
10

Aquí un buen pequeño combinador:

ensure :: MonadPlus m => (a -> Bool) -> (a -> m a) 
ensure p x = guard (p x) >> return x 

Ahora podemos escribir una pura función que comprueba su predicado y aplica f cuando sea apropiado:

process :: String -> Maybe String 
process = fmap f . ensure pred 

elevación esto a una acción IO es simplemente otra fmap:

result = fmap process getString 

Personalmente, me gustaría probablemente en línea process, y escriba esto de esta manera:

result = fmap (fmap f . ensure pred) getString 

... que es una descripción relativamente limpia de lo que está sucediendo.

Cuestiones relacionadas