2017-07-17 3 views
4

Je suis en train de mettre en œuvre par exemple Applicative pour un tel type:exemple Applicative pour l'Etat - pour des flux de données

newtype State s a = State {runState :: s -> (a, s)} 

J'ai quelques idées différentes pour (< *>) fonction. Une façon de mettre en œuvre qui me vient à l'esprit est

(<*>) :: State s (a -> b) -> State s a -> State s b 
State f <*> State s = State $ do 
    (fa, fs) <- f 
    let (sa, ss) = s fs 
    return (fa sa, ss) 

Ou

(<*>) :: State s (a -> b) -> State s a -> State s b 
State f <*> State s = State $ do 
    (sa, ss) <- s 
    let (fa, fs) = f ss 
    return (fa sa, fs) 

Lequel (ou fait même l'un d'eux) est correcte et pourquoi?

Ils sont tous les deux typecheck et ne diffèrent que par l'ordre des transformations "state". Je ne trouve pas de bonne raison de préférer l'un à l'autre ...

+1

Cette question semble être une bonne occasion de [nerd snipe] (https://xkcd.com/356/). Voici une définition déroutante (mais valide!) De '>> =' pour une monade 'State' pour que vous pensiez à:' State f >> = g = État $ \ s -> let {(u, x) = ft; (t, y) = runState (g x) s} dans (u, y) '. Observez comment 'x',' y' vont de haut en bas mais 's',' t', 'u' vont de bas en haut. Toujours fait mal au cerveau :) –

+1

Si vous ne l'avez pas vu, [vous pourriez aimer ça] (https://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-reverse-state-monad/) – luqui

+0

@BenjaminHodgson C'est bizarrement beau – 4castle

Répondre

4

Je crois que les deux sont corrects, car ils ne violent pas les lois d'application autant que je peux voir. Cependant, le premier est ce qui est réellement utilisé. Je pense que c'est à cause de la convention: on s'attend à ce que les effets des arguments de gauche de <*> s'appliquent d'abord, avant son argument de droite. Comparer avec IO, par exemple, où

(,) <$> readLn <*> getLine :: IO (Int, String) 

invites pour le premier Int, puis lit une chaîne. C'est agréable de voir l'État se comporter de la même manière.

+3

Je ne pense pas que ce soit complètement vrai, en supposant qu'une instance de 'Monad' est donnée pour 'State'. Les lois ['Applicative'] énoncent (https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Applicative.html#t:Applicative) que si' f' est aussi 'Monad' , il devrait satisfaire '(<*>) = ap', que la première définition violerait, en supposant l'instance' Monad' habituelle. –

4

Les deux sont raisonnables. Notez que vous pouvez obtenir l'un d'eux de l'autre:

x <*2> y = flip ($) <$> y <*1> x 

Il est une convention de bibliothèque, cependant, que les « effets » sont effectués de gauche à droite. Par conséquent, la première version semble plus familier.

4

Tout d'abord, je vous recommande de ne pas utiliser la syntaxe (monadique!) do pour définir une instance applicative comme ça, car elle obscurcit plutôt ce qui se passe. Voici vos définitions en utilisant uniquement la syntaxe fonctionnelle standard:

State f <*> State s = State $ \q 
    -> let (fa, fs) = f q 
      (sa, ss) = s fs 
     in (fa sa, ss) 

et

State f <*> State s = State $ \q 
    -> let (fa, fs) = f ss 
      (sa, ss) = s q 
     in (fa sa, fs) 

Cela rend également plus clair qu'il n'y a pas vraiment de l'ordre d'évaluation intrinsèque dans une instance applicative (contrairement à une instance monade).

+0

Merci pour le conseil! Je vais essayer de ne pas abuser de cette notation alors. – Ikciwor

+2

@Ikciwor Il n'y a rien de mal à utiliser cette notation en général. Le conseil est de ne pas l'utiliser * ici * car il est tributaire des dépendances: Monad est une fonction plus puissante construite sur Applicative, donc utiliser la notation monadique pour définir '<*>' est comme une dépendance circulaire. Bien sûr, en pratique, vous pouvez les définir dans l'un ou l'autre ordre si vous savez que les deux existeront, mais quand vous essayez de comprendre comment ils fonctionnent, il est préférable de les construire dans l'ordre du moins au plus puissant. – amalloy

+1

Exactement, c'est ce que je veux dire - monad est strictement plus spécial que applicatif. Ce n'est pas vraiment essentiel ici parce que le 'do' utilise une monade complètement différente de celle que vous définissez (fonction/monad), mais encore une fois c'est une instance de monade qui vous donne très peu qui ne pouvait pas être écrit aussi bien ou mieux avec des lambdas explicites etc., donc j'évite cette monade spécifique assez généralement. – leftaroundabout