2014-07-23 18 views
17

Prendiamo due funzioni:Piano B, o qual è l'opposto di Maybe's >> =?

f :: a -> Maybe b 
g :: b -> Maybe c 

La funzione >>= potrebbe funzionare in modo tale che sarebbe f >>= g eseguire g con il risultato di f solo se non è Nothing. In altre parole, richiede sia f che g per riuscire a produrre qualsiasi risultato.

Sto implementando un parser e mi sono reso conto che il mio lexer avrebbe beneficiato di un risultato opposto. Cioè:

f :: a -> Maybe b 
g :: a -> Maybe b 

planb :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b) 
planb f g = \x -> case f x of 
         Nothing -> g x 
         res -> res 

che significa provare f e se non riesce, provare g come un piano di backup. Con un lexer significa provare ad abbinare un tipo di token con l'input corrente e, se fallisce, prova ad abbinare un altro tipo di token (che alla fine sarà incatenato per tutti i tipi di token).

La ricerca di Hoogle non ha comportato alcuna di tali funzioni, ma per me una tale funzione sembra essere utile in molti posti!

La mia domanda è quindi se esiste già una variante di planb che dovrei usare? Se no, sto facendo qualcosa di straordinario e c'è un modo migliore per ottenere ciò che voglio?


P.S. Ho pensato se una tale funzione ha senso per Monad s in generale, ma non ha molto senso per me al di fuori di Maybe e forse pochi altri.

+8

suono come cerchi [ 'MonadPlus'] (http://www.haskell.org/haskellwiki/MonadPlus). – Franky

+0

@Franky dovresti fare una risposta. –

+0

BTW Non vedo un motivo per un lambda qui. 'planb f g x = case f x ...' funziona. Forse la parentesi (inutile) intorno all'estrema destra 'a -> Forse b' ha oscurato la tua vista. Forse è solo una questione di preferenza. – Franky

risposta

20

Il Alternative typeclass fa proprio questo, è del tutto simile a MonadPlus ma forse un po 'più generale.

import Control.Applicative 

-- most general form 
planb :: (Applicative g, Alternative f) => g (f a) -> g (f a) -> g (f a) 
planb = liftA2 (<|>) 

-- specialized to (->) and Maybe 
planb' :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b) 
planb' = planb 

-- equivalent to planb' (and planb) but without the fancy combinators 
planb'' :: (a -> Maybe b) -> (a -> Maybe b) -> a -> Maybe b 
planb'' f g x = f x <|> g x 

Collegando questo per un semplice caso di test:

test :: Maybe Int 
test = do 
    a <- planb' (const Nothing) id (Just 1) 
    b <- planb' id id (Just 1) 
    c <- planb' id (const Nothing) (Just 1) 
    return $ a + b + c 

genera il risultato atteso:

*Main> test 
Just 3 
+0

Non avevo prestato attenzione che l'ascensore può essere usato per aggiungere' a -> 'a un tipo. È allo stesso tempo intrigante e strano che '(->) r' è anche un Monade (, Applicativo e Funzionale). Mentre l'altra risposta sarebbe più semplice, andrò con la tua quindi imparerò meglio attraverso l'esperienza. Grazie! – Shahbaz

+0

No forse a questo proposito, 'Alternative' diventerà una superclasse di' MonadPlus' nel grande shakeup di GHC 7.10 AMP. –

+1

Link alla pagina haskellwiki sulla Proposta di Monad Applicativo (AMP) [qui] (http://www.haskell.org/haskellwiki/Functor-Applicative-Monad_Proposal) – AndrewC

6

Nota che la funzione planb realtà ha solo bisogno di operare su Maybe valori; chiamare le funzioni per produrle può essere scomposto.

planb :: Maybe a -> Maybe a -> Maybe a 
planb Nothing b = b 
planb a _ = a 

E che ci si chiami come planb (f x) (g x) per ottenere un risultato Maybe.

Con questo in mente, dare un'occhiata al MonadPlus class (come suggerito da Franky in un commento):

planb = mplus 

Potreste anche essere interessati a msum, che prende una lista di Maybe valori e restituisce il il primo (se presente) che non è Nothing. Ecco una comoda funzione:

matchSomehow :: [a -> Maybe b] -> a -> Maybe b 
matchSomehow fs a = msum $ map ($a) fs 
+0

La definizione di 'planb' come questo significa che non potrei scrivere qualcosa come questo: 'testAll = testThis \' planb \ 'testThat \' planb \ 'testHere \' planb \ 'testThere', ma dovrei applicare l'argomento della funzione a ognuno di essi. Ottimo punto con l'ultimo però, 'testAll = planb [testThis, testThat, testHere, testThere]' funzionerebbe (e non devo scrivere la piega come farei con la risposta 'Alternative'! – Shahbaz

+2

Invece di scrivere foldare, puoi usare ['Data.Foldable.asum'] (http://hackage.haskell.org/package/base/docs/Data-Foldable.html#v:asum) per' Alternative' proprio come questo risposta usa 'msum' per MonadPlus. – Toxaris