2015-11-08 8 views
6

Mi chiedo se esiste un modo idiomatico di scrivere il codice di controllo simile alle dichiarazioni concatenate se/else in un linguaggio imperativo nell'IO Monade.Concatenato se/else staments nell'IO Monade

Quindi, in un linguaggio come Python, vorrei qualcosa di tipicamente destra come questo:

if οs.path.isdir(fname): 
    # do whatever 
elif os.path.isfile(fname): 
    # ... 
else: 
    # ... 

il meglio che potessi venire in Haskell è la seguente:

isf <- doesFileExist path 
isd <- if isf then return False else doesDirectoryExist path 
case (isf, isd) of 
    (True, _)  -> return ... 
    (_,  True) -> return ... 
    _    -> return ... 

che non è così buono e mi chiedo se c'è un modo migliore per scrivere questo genere di cose.

Inoltre, per convalidare la mia comprensione: la parte è richiesta nel caso della Monade IO, se non si desidera eseguire sempre entrambe le operazioni. La mia ipotesi sarebbe che in altre Monadi (Monad Lazy?), Questo non sarebbe necessario perché isd sarebbe valutato pigramente.

Modifica

Sulla base dei primi commenti, ho finito con il seguente:

firstMatchM :: (Monad m) => a -> [(a -> m Bool, b)] -> b -> m b 
firstMatchM arg [] def = return def 
firstMatchM arg ((check,x):xs) def = do 
    t <- check arg 
    if t then return x else firstMatchM arg xs def 

doFirstM :: (Monad m) => a -> [(a -> m Bool, a -> m b)] -> (a -> m b) -> m b 
doFirstM arg acts def = do 
    fm <- firstMatchM arg acts def 
    fm arg 

handlePath2 path = doFirstM path 
    [(\p -> doesFileExist p, 
     \p -> return "file" 
    ),(\p -> doesDirectoryExist p, 
     \p -> return "dir" 
    )] $ \p -> return "Error" 

che è simile al secondo suggerimento @ di chi, po preferisco ifM, perché è più vicino al versione imperativa.

+0

ma anche in python si dovrebbe refactoring quelli;) – Carsten

+0

OK, sarò morso, come? :) – ynimous

+0

Beh, si potrebbe fare qualcosa come costruire un elenco di azioni e usare cose come 'foldM' o' forM' ecc. Per ottenere il risultato desiderato. Questo sarebbe generalizzato a qualsiasi numero di 'elif's anche se sarebbe ingombrante solo per 3 casi. Lo stesso sarebbe vero in Python: 'per test, azione in test_and_actions: se test (input): action (input)'. – Bakuriu

risposta

4

Se non vogliamo coinvolgere trasformatori monade, un'opzione di base è a rotazione nostra monade if:

ifM :: Monad m => m Bool -> m a -> m a -> m a 
ifM act t e = do 
    b <- act 
    if b then t else e 

Poi la struttura del codice è simile a quella di linguaggi imperativi:

test :: IO String 
test = ifM anAction (do 
      putStrLn "branch a" 
      return "a") 
     $ ifM otherAction (do 
      putStrLn "branch b" 
      return "b") 
     $ return "none" 

dove anAction, otherAction :: IO Bool.

In alternativa, usare qualcosa come

ifChain :: [(IO Bool, IO a)] -> IO a -> IO a 
ifChain [] e = e 
ifChain ((g, a) : acts) e = do 
    b <- g 
    if b then a else ifChain acts e 
+0

@Bakuriu Destra. – chi

+0

Mi piace il 'ifM' meglio, penso. Come lo faresti con i trasformatori monadici? – ynimous

+0

@ynimous Ho provato i trasformatori monad ('MaybeT'), ma il risultato non mi è piaciuto: troppi' lift's e codice boilerplate. Forse qualcun altro può pubblicare un codice che è in realtà leggibile. – chi