Skip to content

Instantly share code, notes, and snippets.

@paul-r-ml
Created December 7, 2011 16:40
Show Gist options
  • Save paul-r-ml/1443519 to your computer and use it in GitHub Desktop.
Save paul-r-ml/1443519 to your computer and use it in GitHub Desktop.
simple reader monad
require 'funkr/types'
class Reader
def initialize(func); @func = func; end
def run(env); @func.call(env); end
# Pour l'usage du foncteur applicatif
def map(&block); reader{|env| block.call(run(env))}; end
def self.pure(v); reader{|_env| v}; end
def apply(to); reader{|env| run(env).call(to.run(env))}; end
# Pour l'usage monadique
def bind(&block)
reader{|env| block.call(run(env)).run(env)}
end
# Quelques utilitaires
def self.ask; reader{|env| env }; end
def self.chain(&block); ask.bind(&block); end
def reader(&block); self.class.new(block); end
def self.reader(&block); self.new(block); end
end
JULIEN = Funkr::Types::SimpleRecord.new(name: "Julien", age: 25)
module ReaderMonadTest
def self.presentation
name_part.bind{|name| age_part.bind{|age|
Reader.pure( [name, ", ", age, "."].join )
} }
end
def self.name_part
Reader.chain{|p| Reader.pure( "Bonjour je m'appelle " + p.name ) }
end
def self.age_part
Reader.chain{|p| Reader.pure( "et j'ai " + p.age.to_s + " ans") }
end
end
module ReaderApplicativeTest
def self.presentation
aconcat([name_part, Reader.pure(", "), age_part, Reader.pure(".")])
end
def self.name_part
aconcat([Reader.pure("Bonjour je m'appelle "), Reader.ask.map(&:name)])
end
def self.age_part
aconcat([Reader.pure("et j'ai "), Reader.ask.map{|p| p.age.to_s}, Reader.pure(" ans")])
end
def self.aconcat(readers)
readers.inject{|a,e| a.map{|x| lambda{|y| x + y}}.apply(e)}
end
end
puts ReaderMonadTest.presentation.run(JULIEN) # => Bonjour je m'appelle Julien, et j'ai 25 ans.
puts ReaderApplicativeTest.presentation.run(JULIEN) # => Bonjour je m'appelle Julien, et j'ai 25 ans.
-- Le principe du reader peut être utilisé avec un simple foncteur applicatif,
-- qui est moins spécifique que les monades. Exemple :
import Control.Applicative
-- Un type qui sera une instance de Applicative. 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
instance Functor (Reader env) where
fmap g (Reader f) = Reader (g . f)
instance Applicative (Reader env) where
pure x = Reader $ const x
(Reader f) <*> (Reader x) = Reader $ \env -> (f env) (x 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
presentation :: Reader Person String
presentation = aconcat [getNamePart, pure ", ", getAgePart]
getNamePart, getAgePart :: Reader Person String
getNamePart = aconcat [pure "Bonjour je m'appelle ", name <$> ask]
getAgePart = aconcat [pure "et j'ai ", (show . age) <$> ask, pure " ans"]
aconcat :: (Applicative ap) => [ap String] -> ap String
aconcat = foldl1 (liftA2 (++))
-- Quand le programme est executé, il affiche "Bonjour je m'appelle
-- Julien, et j'ai 25 ans."
-- 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."
@paul-r-ml
Copy link
Author

par souci de solidarité dans l'usage difficile du mélange objet-fonctionnel, j'ai ajouté une variante en Ruby :)
Il y a le style monadique et le style applicatif. C'est un peu moche mais ça marche.

@julienrf
Copy link

Sympa :)
Ça fait quoi le & dans la définition des paramètres pris par une fonction Ruby ?

@paul-r-ml
Copy link
Author

en ruby il y a deux moyens de construire un lambda.
Le premier moyen marche partout, et se fait avec le mot-clef lambda suivi d'un bloc paramétré. Par exemple lambda{|x,y| x+y}.
Le second moyen est un cas particulier pour les fonctions d'ordre supérieur qui nécessitent, de toute façon, toujours un lambda lors de l'appel. Dans ce cas, pour économiser à l'appelant l'effort de taper "lambda", le dernier paramètre de la fonction peut être noté &bidule, comme dans "def fonction(p1, p2, &bidule)". L'appelant pourra alors simplement taper fonction(v1,v2){|x| machin(x)}.
Typiquement : [1,2,3].map{|x| x+1} va donner [2,3,4].

Ce que j'en pense : c'est une bidouille peu générique mais commode, introduite pour rendre plus agréable l'usage des fonctions d'ordre sup. paramétrées par UNE fonction ano..

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