-
-
Save paul-r-ml/1443519 to your computer and use it in GitHub Desktop.
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." |
petite pause de midi : le Reader sans la monade :)
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.
Sympa :)
Ça fait quoi le &
dans la définition des paramètres pris par une fonction Ruby ?
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..
Tiens, finalement c’était assez simple, je l’ai mis ici : https://gist.github.com/1448561#file_reader.scala
Finalement, je trouve que c’est presque plus simple en Scala qu’en Haskell :p (pas besoin des fonctions
runReader
niask
)