2013-02-27 14 views
18

OK, so weiß ich, was die Typklasse enthält, und warum das nützlich ist. Aber ich kann mir nicht vorstellen, wie Sie es in einem nicht-trivialen Beispiel verwenden würden.Übersetzen von Monad zu Applicative

Betrachten wir zum Beispiel die folgenden ziemlich einfach Parsec Parser: würden Sie

integer :: Parser Integer 
integer = do 
    many1 space 
    ds <- many1 digit 
    return $ read ds 

Nun, wie zum Teufel schreiben, dass ohne die Monad Instanz für Parser zu verwenden? Viele Leute behaupten, dass dies getan werden kann und es ist eine gute Idee, aber ich kann nicht herausfinden, wie genau.

Antwort

11
integer :: Parser Integer 
integer = read <$> (many1 space *> many1 digit) 

Oder

integer = const read <$> many1 space <*> many1 digit 

Ob denken Sie entweder diese besser lesbar sind, ist Ihnen überlassen.

+0

Warum die 'const'? – MathematicalOrchid

+1

Wir wollen den Wert (aber nicht den Effekt) von 'many1 space' ignorieren und' read' auf den Wert von 'many1 digit' anwenden. (Sorry, ich bin gerade erst reingekommen, es ist spät, ich bin müde: Ich spiele schnell und locker mit der Terminologie.) Wenn Sie sich vorstellen, 's' und' d' repräsentieren die Werte von 'many1 space' und' many1 digit', dann ist der Wert (Ignoriereffekte) von 'const read <$> many1 space <*> many1 digit' ist 'const read sd' = 'read d'. – dave4420

38

würde ich

integer :: Parser Integer 
integer = read <$ many1 space <*> many1 digit 

Es gibt eine Reihe von links assoziativ (wie Anwendung) Parser-Gebäudebetreiber <$>, <*>, <$, <* schreiben. Das Ding ganz links sollte die reine Funktion sein, die den Ergebniswert aus den Komponentenwerten zusammensetzt. Die Sache rechts von jedem Operator sollte ein Parser sein, der zusammen die Komponenten der Grammatik von links nach rechts gibt. Welcher Operator verwendet wird, hängt von zwei Möglichkeiten ab, wie folgt.

the thing to the right is signal/noise 
    _________________________    
    the thing to the left is \   
          +------------------- 
        pure/| <$>  <$ 
        a parser | <*>  <* 

So read :: String -> Integer als reine Funktion gewählt zu haben, die die Semantik des Parsers liefern wird, können wir den führenden Platz als „Rauschen“ und das Bündel von Ziffern als „Signal“ klassifizieren, daher

read <$ many1 space <*> many1 digit 
(..) (.........)  (.........) 
pure noise parser  | 
(.................)  | 
    parser    signal parser 
(.................................) 
        parser 

Sie können mit mehreren Möglichkeiten kombinieren

p1 <|> ... <|> pn 

und Express Unmöglichkeit mit

empty 

Es ist selten notwendig, Komponenten in Parsern zu benennen, und der resultierende Code sieht eher wie eine Grammatik mit hinzugefügter Semantik aus.

+8

Wow, ich wusste von '<$', aber ich benutzte es nur, wenn das Ding links davon eine Konstante war und das Recht ein einfacher Wert war ... Ich habe nie darüber nachgedacht, was passieren würde, wenn ich eine Funktion einstelle nach links: P Netter Trick –

7

Ihr Beispiel kann schrittweise in eine Form umgeschrieben werden, die mehr ähnelt eindeutig eine Applicative:

do 
    many1 space 
    ds <- many1 digit 
    return $ read ds 
  1. Definition do Notation:

    many1 space >> (many1 digit >>= \ds -> return $ read ds) 
    
  2. Definition von $:

    many1 space >> (many1 digit >>= \ds -> return (read ds)) 
    
  3. Definition von .:

    many1 space >> (many1 digit >>= (return . read)) 
    
  4. 3. monadisch Gesetz (Assoziativität):

    (many1 space >> many1 digit) >>= (return . read) 
    
  5. Definition von liftM (in nicht do Notation):

    liftM read (many1 space >> many1 digit) 
    

Dies ist (oder sollte, wenn ich nicht versaut habe :)) identisch im Verhalten zu Ihrem Beispiel.

Nun, wenn Sie liftM mit fmap mit <$> ersetzen und >> mit *>, erhalten Sie die Applicative:

read <$> (many1 space *> many1 digit) 

Dies gilt, weil liftM, fmap und <$> allgemein Synonyme sein sollen, wie sind >> und *>.

Das alles funktioniert und wir können dies tun, weil das ursprüngliche Beispiel das Ergebnis eines Parsers nicht verwendet hat, um einen folgenden Parser zu erstellen.

+0

Cool! Eine andere Art zu schreiben 'lesen <$ many1 Leerzeichen <*> many1 digit. :) Der letzte Satz ist sehr wichtig. Bedeutet das, dass dieser Stil kontextfreien Grammatiken entspricht und allgemeinere Grammatiken im monadischen Stil analysiert werden müssen? –

+0

@WillNess Ich bin kein Experte dafür, aber ich glaube, das ist der Fall. –