2011-10-07 12 views
14

Mam następujący kod, ale uważam, że jest zbyt brzydki i konieczny. Czy ktoś mógłby ją przeformułować, aby był bardziej funkcjonalny? (Pomieszałem z MaybeT, ale nie mogłem sprawić, żeby to działało) Odpowiedzi na aprobaty również są mile widziane.IO i Być może interakcja z monadą

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: a follow-up pytanie: co, jeśli zarówno pred if również zwracać wyniki w ciągu IO (należy podzielić na to uwagę w osobnym pytaniu?)

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 

Odpowiedz

25

bym zacznij od przyjęcia logiki tutaj z monady IO. Czynność można następnie zapisać jako

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

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

Można chyba napisać foo na różne sposoby za pomocą wymyślnej kombinatorów, ale nie sądzę, że jest to konieczne tutaj. Najważniejsze jest, aby logika została usunięta z IO, aby łatwiej ją było przetestować.

+6

+1 Nie można zaprzeczyć, że uzyskanie logiki z 'IO' jest ważniejsze niż posiadanie fantazyjnego rozwiązania jednokreskowego. – leftaroundabout

5
import Control.Monad 

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

Zauważ, że 'mfilter' zastępuje twoje if-then-else. – fuz

+2

'result = (f <$> mfilter pred.Po prostu) <$> getString ' – fuz

+0

@FUZxxl, który nie sprawdza typu. 'mfilter pred. Po prostu :: a -> Może a', ale 'f <$> x 'oczekuje' x :: Może a'. Potrzebujesz jakiegoś aplikacyjnego operatora kompozycji 'f <.> g = \ x -> f <$> g x', który jeśli podano niższy priorytet niż' .' pozwala na 'result = (f <.> mfilter pred. Just) <$> getString'. Ponadto, zastąpienie 'Just' przez' return' pozwala na użycie dowolnego 'Monada'. – pat

3

nie można łatwo uciec z przylegający if-then-else, ale można uciec z nadmiarowa 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

Oczywistym transformacja z kodu jest czynnik return operacje:

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

Dzięki temu wzoru bardziej pozorna

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

Stosując f z zewnątrz, możemy zrobić następny wzór pozorny:

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

który pozwala nam remplace blok if:

g = liftM f . mfilter pred . return 

Reasumując:

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

Oto miły mały kombinator:

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

Teraz możemy napisać funkcję, która sprawdza czystego swoje orzeczenie i stosuje f gdy właściwe:

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

podnoszenia to działania IO jest po prostu inny fmap:

result = fmap process getString 

Osobiście bym prawdopodobnie inline process i napisz w ten sposób:

result = fmap (fmap f . ensure pred) getString 

... który jest względnie czystym opisem tego, co się dzieje.

Powiązane problemy