Skip to content

Instantly share code, notes, and snippets.

@julienrf
Forked from paul-r-ml/reader-applicative-monadic.rb
Created December 8, 2011 21:00
Show Gist options
  • Save julienrf/1448561 to your computer and use it in GitHub Desktop.
Save julienrf/1448561 to your computer and use it in GitHub Desktop.
simple reader monad
-- Un type qui sera une instance de Monad. Il s'agit d'un simple
-- conteneur pour les fonctions d'un environnement vers un résultat.
newtype Reader env res = Reader (env -> res)
-- Une fonction qui prend un Reader et un environnement, et qui
-- renvoit le résultat
runReader :: Reader env res -> env -> res
runReader (Reader f) e = f e
-- La fameuse instance de Monad. return ignore son environnement,
-- c'est en fait le conteneur de la fonction constante. bind execute
-- la fonction en lui fournissant l'environnement.
instance Monad (Reader env) where
return x = Reader $ \_ -> x
(Reader f) >>= g = Reader $ \env -> runReader (g $ f env) env
-- Obtenir le contenu de l'environnement comme résultat
ask :: Reader e e
ask = Reader id
-- Utilisation
data Person = Person { name :: String, age :: Int }
julien :: Person
julien = Person "Julien" 25
-- affiche le résultat d'une fonction qui construit la présentation
main :: IO ()
main = putStrLn $ show $ runReader presentation julien
-- tu m'as dit que tu aimais les "do" alors en voila ...
presentation :: Reader Person String
presentation = do
namePart <- getNamePart
agePart <- getAgePart
return $ concat [namePart, ", ", agePart, "."]
-- les sous-parties, sans le 'do' ce coup-ci pour changer
getNamePart, getAgePart :: Reader Person String
getNamePart = ask >>= \p ->
return $ concat ["Bonjour je m'appelle ", name p]
getAgePart = ask >>= \p ->
return $ concat ["et j'ai ", show $ age p, " ans"]
-- Quand le programme est executé, il affiche "Bonjour je m'appelle
-- Julien, et j'ai 25 ans."
package reader
object Main extends App {
// Le type Reader, et ses méthodes map et flatMap, nécessaires pour être utilisées dans la notation “monad-comprehension”
class Reader[Env, Res](val read: Env => Res) {
def map[A](g: Res => A): Reader[Env, A] = Reader((env: Env) => g(f(env)))
// Tu remarqueras que cette fonction est équivalente à “bind”
def flatMap[A](g: Res => Reader[Env, A]): Reader[Env, A] = Reader((env: Env) => g(f(env)) read env)
}
object Reader {
// L’équivalent de “pure”. Cette fonction est appelée quand j’écris “Reader(…)” (sans le “new”)
def apply[E, R](f: E => R) = new Reader(f)
}
// Utilisation
case class Person(name: String, age: Int)
val paul: Person = Person("Paul", 26)
// Appelle la méhode “read” du Reader construit par la fonction “presentation”
println(presentation read paul)
// Enfin, la notation monad-comprehension !
// En fait c’est juste un sucre syntaxique pour:
// “getNamePart.flatMap(namePart => getAgePart.map(agePart => namePart + ", " + agePart + "."))”
def presentation: Reader[Person, String] = {
for {
namePart <- getNamePart
agePart <- getAgePart
} yield namePart + ", " + agePart + "."
}
// Construit un Reader qui fournit une String à partir d’une Person
def getNamePart = Reader { (p: Person) => "Bonjour je m’appelle " + p.name }
def getAgePart = Reader { (p: Person) => "et j’ai " + p.age + " ans" }
}
// Au final, le programme affiche : « Bonjour je m’appelle Paul, et j’ai 26 ans. »
object Main extends App {
// Un conteneur de fonction de Env vers Res
class Reader[Env, Res](val f: Env => Res)
// Prend un Reader et un environnement et renvoie le résultat de l’application du Reader
def runReader[E, R](reader: Reader[E, R], env: E) = reader.f(env)
// La typeclass Monad, car elle n’est pas prédéfinie dans Scala
// Tu peux voir https://github.com/scalaz/scalaz pour une bibliothèque qui implémente la plupart des typeclasses de Haskell en Scala
trait Monad[M[_]] {
def pure[A](x: A): M[A]
def bind[A, B](m: M[A])(f: A => M[B]): M[B] // J’ai mis “bind” plutôt que “>>=” car cette fonction ne sera pas utilisée en syntaxe infixe
}
// Un type qui me facilitera l’écriture de l’application partielle du type Reader
trait ReaderE[E] {
type λ[X] = Reader[E, X]
}
// L’instance de Monad
implicit def ReaderMonad[E] = new Monad[ReaderE[E]#λ] {
def pure[A](x: A) = new Reader[E, A]((_: E) => x)
def bind[A, B](reader: Reader[E, A])(g: A => Reader[E, B]): Reader[E, B] =
new Reader((env: E) => runReader(g(reader.f(env)), env))
}
def ask[E]: Reader[E, E] = new Reader[E, E](e => e)
// Utilisation
case class Person(name: String, age: Int)
val paul: Person = Person("Paul", 26)
// Affiche le résultat de la fonction presentation
println(runReader(presentation, paul))
// Je ne peux malheureusement pas utiliser la notation “monad-comprehension” directement ici car
// elle n’est pas intégrée avec le style purement fonctionnel en Scala
def presentation(implicit m: Monad[ReaderE[Person]#λ]): Reader[Person, String] = {
m.bind (getNamePart) { namePart =>
m.bind (getAgePart) { agePart =>
m pure (namePart + ", " + agePart + ".")
}
}
}
def getNamePart(implicit m: Monad[ReaderE[Person]#λ]): Reader[Person, String] =
m.bind (ask[Person]) { p: Person => m pure ("Bonjour je m’appelle " + p.name) }
def getAgePart(implicit m: Monad[ReaderE[Person]#λ]): Reader[Person, String] =
m.bind (ask[Person]) { p: Person => m pure ("et j’ai " + p.age + " ans") }
}
// Au final, le programme affiche : « Bonjour je m’appelle Paul, et j’ai 26 ans. »
@julienrf
Copy link
Author

I’m a bit surprised because I didn’t know that syntax, I thought the :: were only used to write type anotations, but besides that I think it’s ok. Actually it made me realize that I could use the same trick in Scala: I’ve modified the Reader class to copy md2perpe’s style :)

@md2perpe
Copy link

@paul-r-ml: But you use this record syntax yourself in the Person type.

@paul-r-ml
Copy link

@md2perpe : indeed, but I try to only use it for product types (tuple types), for the sake of naming constructor fields. In my opinion, this is the original design for record syntax. A person obviously is composed of a 'name' and an 'age'. But a reader is not obviously composed of a 'runReader' :)
Again, that's really a matter of taste.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment