2017-10-13 3 views
9

Ich lese Purescript von Beispiel und kam zu dem Teil Einführung der Reader Monad. Das Beispiel geht so:Verständnis der Reader Monad

createUser :: Reader Permissions (Maybe User) 
createUser = do 
    permissions <- ask 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

Das verwirrende Teil ist für mich die ask Funktion. Die Signatur ist:

ask :: forall r. Reader r r 

Es scheint, als ob es einen Leser aus der Luft erzeugt

Wenn ich über die State Monade las, es das gleiche Konzept mit seiner get Funktion hatte. Und der Text erklärt:

der Zustand wird als ein Funktionsargument implementiert, das durch den Datenkonstruktor der Zustands-Monade versteckt wird, also gibt es keinen ausdrücklichen Hinweis, um herumzugehen.

Ich vermute, dies ist der Schlüssel, und gleiche geschieht hier mit dem Reader, aber ich verstehe nicht, wie es funktioniert ...

Wenn das obige Beispiel laufen über runReader, wie erscheint plötzlich der angegebene Wert als Ergebnis von ask? Die Haskell-Dokumente für ask sagen: Ruft die Monad-Umgebung ab. Aber meine Verwirrung ist von wo? So wie ich es sehe, wird ein Wert an runReader übergeben, wird irgendwo gespeichert, und um es zu bekommen - rufen Sie ask ... aber das macht keinen Sinn.

Während das Beispiel Purescript ist, nehme ich an, dass jede Haskell-fähige Person auch antworten könnte, daher der Haskell-Tag.

+2

'Reader ein b' ist ein Wrapper um' a -> b '. Also, fragen Sie :: Forall a. Reader a a' ist letztlich nur ein Wert des Typs 'forall a. a -> a', mit einer Umhüllung. –

Antwort

8

Ich habe derzeit keine PureScript-Umgebung, also werde ich versuchen, aus einer Haskell-Perspektive zu antworten, und hoffe, es hilft.

A Reader ist wirklich nur ein 'Wrapper' um eine Funktion, so dass, wenn Sie ein Reader r r erhalten, die Sie wirklich nur einen Leser von r zu r bekommen; mit anderen Worten, eine Funktion r -> r.

Sie können aus der Luft Funktionen rufen, weil, wenn Sie einen Platoniker sind, ich nehme an, sie existieren immer ...

Wenn Sie do Schreibweise verwenden, du bist ‚innerhalb der Monade‘ , so ist der Kontext r implizit. Mit anderen Worten, Sie rufen eine Funktion auf, die den r Wert zurückgibt, und wenn Sie den <- Pfeil verwenden, erhalten Sie einfach diesen Kontext.

+2

Upvoted. Wahrscheinlich ist es nichts wert, dass die Art, eine solche "r -> r" -Funktion aus der Luft zu rufen, die Beschwörungsformel "id" ist. (Und 'ID' ist die _only_ solche Funktion, dank der Parametrisierung.) –

+0

Ok, also in meinem Fall ist der Reader ein Wrapper um Berechtigungen -> Vielleicht Benutzer. Wenn ich 'runReader createUser permissions 'ausführe, weiß ich, wie' ask' im Hauptteil von 'createUser' die gleichen' Berechtigungen' zurückgibt, die ich an 'runReader' übergeben habe? Ich bin mir sicher, dass die Frage absolut unsinnig ist, da ich alles falsch sehe ... Aber bitte versuch mir zu helfen, mein Gehirn zu öffnen. – kaqqao

+1

@kaqqao Der Typ 'Reader Permissions (Maybe User)' ist nur ein 'Wrapper' über 'Permissions -> (Maybe User)', also ist Ihr gesamter 'createUser' 'Wert' wirklich eine Funktion (aber Funktionen sind Werte, also Das ist cool). Wenn Sie 'runReader' aufrufen, müssen Sie nicht nur' createUser' übergeben, sondern auch einen Wert für 'r' - in diesem Fall einen' Permissions'-Wert. 'runReader' ruft dann die umgebrochene Funktion mit dem Wert' Permissions' auf, den Sie übergeben haben. HTH. –

1

Sie können sich davon überzeugen, dass es funktioniert, indem Sie ein paar Substitutionen durchführen. Schauen Sie sich zuerst die Signatur createUser an. Lassen Sie sich „abrollen“ die Definition von Reader:

createUser :: Reader Permissions (Maybe User) 
{- definition of Reader -} 
createUser :: ReaderT Permissions Identity (Maybe User) 

Der ReaderT Typ hat nur ein Datum Konstruktor: ReaderT (r -> m a), die createUser bedeutet ist ein Begriff, ReaderT (Permissions -> Identity (Maybe User)) auf einen Wert von Typ auswertet. Wie Sie sehen können, ist es nur eine Funktion, die mit ReaderT markiert ist.Es muss nicht aus der Luft gegriffen werden, sondern erhält den Wert vom Typ Permissions, wenn diese Funktion aufgerufen wird.

Nun schauen wir uns die Zeile an, mit der Sie Probleme haben. Sie wissen, dass die do Notation nur syntaktischer Zucker ist, und der Ausdruck:

do permissions <- ask 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

desugars zu

ask >>= \permissions -> 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

Um zu verstehen, was das bedeutet, werden Sie die Definition von ask, >>= Lookup müssen und pure für ReaderT. Lassen Sie uns eine weitere Runde von Substitutionen durchführen:

ask >>= \permissions -> ... 
{- definition of ask for ReaderT -} 
ReaderT pure >>= \permissions -> ... 
{- definition of >>= for ReaderT -} 
ReaderT \r -> 
    pure r >>= \a -> case (\permissions -> ...) a of ReaderT f -> f r 
{- function application -} 
ReaderT \r -> 
    pure r >>= \a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r 
{- definition of pure for Identity -} 
ReaderT \r -> 
    Identity r >>= \a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r 
{- definition of >>= for Identity -} 
ReaderT \r -> 
    (\a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r) r 
{- function application -} 
ReaderT \r -> 
    case (if hasPermission "admin" r 
     then map Just newUser 
     else pure Nothing) of ReaderT f -> f r 

Wie Sie sehen können, createUser eindeutig nur eine Funktion von ReaderT, die einen Wert Threads (die „Umwelt“) durch Ihre Ausdrücke eingewickelt ist. runReader auspackt die Funktion und ruft sie mit dem mitgelieferten Argument:

runReader :: forall r a. Reader r a -> r -> a 
runReader (ReaderT f) r = f r