2015-12-08 6 views
7

Ich möchte zwei Monaden-Aktionen in Haskell sequenziell verfassen, jeden Wert der Sekunde verwerfen und das Argument an beide Aktionen übergeben. Derzeit verwende ich einen do-Block wie folgt aus:Aktion hinzufügen, ohne das Ergebnis in refactor do-notation zu ändern.

ask = do 
    result <- getLine 
    putStrLn result 
    return result 

Ich hatte gehofft, diese ein wenig mehr Punkt frei und ordentlich zu schreiben, so dass ich versucht, dieses:

ask' = getLine <* putStrLn 

jedoch doesn diese‘ t sogar Typ prüfen und das Problem ist, dass <* das Ergebnis der ersten Aktion nicht auf die zweite übertragen. Ich möchte die Aktionen wie >>= ketten, aber das Ergebnis nicht ändern. Der Typ sollte (a -> m b) -> (a -> m c) -> (a -> m b) sein, aber Hoogle ergibt keine geeigneten Ergebnisse. Was wäre ein Operator, um diese Funktionszusammensetzung zu erreichen?

+1

So möchten Sie 'getLine auch >> = \ x -> putStrLn x >> Rückkehr x' im freien Stil Punkt. Hast du Lambdabot gefragt? Es heißt 'liftM2 (>>) putStrLn return = << getLine'. –

+0

@ ThomasM.DuBuisson Es scheint, als ob Lambdabot fast die genaue Funktion gefunden hätte, die OP wollte! 'flip (liftM2 (>>)) :: Monad m => (a -> mb) -> (a -> mc) -> (a -> mb)' - der tatsächliche Typ ist etwas allgemeiner, so ist es etwas schwer zu sehen. – user2407038

+0

@ ThomasM.DuBuisson Danke, ich fragte das 'pointfree' Befehlszeilen-Tool, und es konnte' do' nicht behandeln, also gab ich auf. Am Ende habe ich 'ask = getLine >> = liftM2 (>>) putStrLn return' verwendet, was gut aussieht, danke nochmal! Sie können dies in eine Antwort einfügen, wenn Sie wollen, dann kann ich es als gelöst markieren. – amoebe

Antwort

8

Als Tendenz, wenn man es ist wahrscheinlich einen Wert in zwei verschiedenen Orten verwendet eine gute Idee, ihm einen Namen in einem klaren do Block zu geben, anstatt auf sinnlos Stil drücken.

Das abstrakte Konzept des Aufteilens des Informationsflusses auf verschiedene Aktionen wird von cartesian monoidal categories, bekannt unter Haskellers, als arrows erfasst. In Ihrem Fall arbeiten Sie im Grunde in der IO Kleisli Kategorie:

import Prelude hiding (id) 
import Control.Arrow 

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

Ich glaube nicht, dass es eine gute Idee, einen solchen Code zu schreiben.

+0

Sie haben Recht, es ist wahrscheinlich keine sehr gute Idee. Die Pfeile sehen sehr kompliziert aus. Ich bin mir nicht sicher, ob die Lösung mit 'liftM2' klar genug oder verwirrend ist. – amoebe

+0

Das Problem mit 'liftM2 (>>)' ist, dass es zwei verschiedene Monaden ('(a ->)' und 'IO') in einer recht recht gewundenen Art und Weise mischt. Wenn Sie so etwas machen, sollten Sie es besser mit 'ReaderT' machen, aber das ist es nur wert, wenn Sie den gleichen Wert aus einer ganzen Reihe von Orten lesen. – leftaroundabout

2

Ich möchte sequentiell zwei Monaden Aktionen in Haskell verfassen, jeden Wert der Sekunde verwerfen und das Argument an beide Aktionen übergeben.

Das klingt für mich wie ein Reader -der Typ Funktion r -> m a-ReaderT r m a isomorph ist, und die Monade wirkt im gleichen r Wert in alle implizit Verstopfen „Löcher“. So zum Beispiel:

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 Sie wollen, ist dann so etwas wie:

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

Aber natürlich discarding hat eine einfachere Implementierung:

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

... so am Ende Ich denke, das ist wirklich Code Golf. Aber in einem komplexeren Programm, in dem dies ein allgemeines Muster in größerem Maßstab ist, könnte es sich lohnen, eine Aufnahme zu machen.Grundsätzlich, wenn Sie haben:

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

Dann folgt, dass:

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

Und dann:

2

Für Vollständigkeit, in diesem speziellen Fall (die IO) Monade, könnten Sie auch Missbrauch bracket für diesen Zweck:

bracket getLine putStrLn return 

Aber ich rate dringend davon ab, da dies viel weniger lesbar sein wird als der ursprüngliche do-Notizblock, es ist einfach hässlich.

Wie bereits erwähnt, scheint in diesem speziellen Fall die Benennung des Ergebnisses der beste Weg zu sein.

Siehe Should do-notation be avoided in Haskell?

Verwandte Themen