2015-12-08 30 views
7

Chcę sekwencyjnie komponować dwie akcje monad w Haskell, odrzucając każdą wartość wytworzoną przez drugą i przekazując argument do obu akcji. Obecnie używam do-bloku tak:Dodaj akcję bez zmiany wyniku do notacji refactora

ask = do 
    result <- getLine 
    putStrLn result 
    return result 

miałem nadzieję napisać trochę więcej punkt darmo i schludny, więc próbowałem to:

ask' = getLine <* putStrLn 

Jednak to robi” t nawet sprawdzanie typu i problem polega na tym, że <* nie przenosi wyniku pierwszej akcji na drugą. Chcę łączyć akcje takie jak >>=, ale nie zmieniać wyniku. Typ powinien być następujący: (a -> m b) -> (a -> m c) -> (a -> m b), ale Hoogle nie daje odpowiednich wyników. Jaki byłby operator, aby osiągnąć ten skład funkcji?

+1

Więc chcesz 'getline >> = \ x -> putStrLn x >> powrót x' w punkcie stylu wolnym. Czy zapytałeś lambdabota? Mówi 'liftM2 (>>) putStrLn return = << getLine'. –

+0

@ ThomasM.DuBuisson Wygląda na to, że Lambdabot znalazł niemal dokładnie taką funkcję, jaką chciał OP! 'flip (liftM2 (>>)) :: Monad m => (a -> mb) -> (a -> mc) -> (a -> mb)' - rzeczywisty typ jest nieco bardziej uogólniony, więc jest nieco trudne do zobaczenia. – user2407038

+0

@ ThomasM.DuBuisson Dziękuję, poprosiłem o narzędzie linii poleceń 'pointfree' i nie radziłem sobie z' do', więc zrezygnowałem. Skończyło się na użyciu 'ask = getLine >> = liftM2 (>>) putStrLn return', który wygląda dobrze, dzięki jeszcze raz! Możesz umieścić to w odpowiedzi, jeśli chcesz, a następnie mogę oznaczyć to jako rozwiązane. – amoebe

Odpowiedz

8

jako tendencja, jeśli używasz jednej wartości w dwóch różnych miejscach, to prawdopodobnie jest to dobry pomysł, aby nadać jej nazwę w jasnym do bloku, a nie naciskając na bezcelowym stylu.

Abstrakcyjna koncepcja podziału strumienia informacji na różne akcje jest przechwytywana przez cartesian monoidal categories, znaną Haskellerowi jako arrows. W twoim przypadku, jesteś w zasadzie pracuje w kategorii IO Kleisli:

import Prelude hiding (id) 
import Control.Arrow 

ask' :: Kleisli IO() String 
ask' = Kleisli (\()->getLine) >>> (putStrLn &&& id) >>> arr snd 

Nie sądzę, że to dobry pomysł, aby napisać taki kod.

+0

Masz rację, prawdopodobnie nie jest to dobry pomysł. Strzały wyglądają na bardzo skomplikowane. Nie jestem pewien, czy rozwiązanie z 'liftM2' w wystarczająco jasne lub mylące? – amoebe

+0

Problem z 'liftM2 (>>)' polega na tym, że miksuje on dwie różne monady ('(a ->)' i 'IO') w naprawdę dość skomplikowany sposób. Jeśli robisz coś takiego, lepiej zrób to poprawnie za pomocą 'ReaderT', ale to jest warte zachodu, jeśli czytasz tę samą wartość z całej gamy miejsc. – leftaroundabout

2

Chcę sekwencyjnie komponować dwie akcje monad w Haskell, odrzucając każdą wartość wytworzoną przez drugą i przekazując argument do obu akcji.

To brzmi dla mnie jak Reader -the typ funkcja r -> m a jest izomorficzna ReaderT r m a i monada działa niejawnie przez podłączenie w tym samym r wartość do wszystkich „dziur”. Tak więc na przykład:

import Control.Applicative 
import Control.Monad.Reader 

example :: IO String 
example = getLine >>= discarding putStrLn 

discarding :: Monad m => (a -> m b) -> a -> m a 
discarding action = runReaderT (ReaderT action *> ask) 

Operator chcesz to wtedy coś takiego:

action `thingy` extra = action >>= discarding extra 

Ale oczywiście discarding ma prostszej realizacji:

discarding :: Applicative f => (a -> f b) -> a -> f a 
discarding action a = action a *> return a 

... więc w końcu Myślę, że to jest naprawdę kod golfowy. Ale w bardziej złożonym programie, w którym jest to powszechny wzorzec na większą skalę, może warto spróbować.Zasadniczo, jeśli masz:

a0 :: r -> m a0 
a1 :: r -> m a1 
    . 
    . 
    . 
an :: r -> m an 

Następnie wynika, że:

ReaderT a0 :: ReaderT r m a0 
ReaderT a1 :: ReaderT r m a1 
    . 
    . 
    . 
ReaderT an :: ReaderT r m an 

, a następnie:

2

Dla kompletności, w tym konkretnym przypadku (IO) monada, można także nadużycie bracket w tym celu:

bracket getLine putStrLn return 

Ale zdecydowanie go zniechęcam, ponieważ będzie to znacznie mniej czytelne niż oryginalny blok notatek, jest po prostu brzydki.

Jak już wspomniano, w tym konkretnym przypadku nazywanie wyniku wydaje się najlepszym sposobem.

Zobacz także Should do-notation be avoided in Haskell?