-
-
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." |
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
ni ask
)
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..
Très intéressant, je me suis amusé à la retranscrire telle quelle en Scala et il apparaît clairement que, si Scala a été outillé pour simuler les typeclasses, ce n’est clairement pas le plus pratique à utiliser (au vu du bruit syntaxique que ça occasionne) : https://gist.github.com/1448561
Si je trouve un peu de temps ce week-end je réfléchirai à une façon de faire plus idiomatique Scala, histoire de voir si j’arrive à proposer quelque chose dont l’utilisation serait aussi élégante qu’en Haskell :)
On peut faire des choses plus sympa, comme le montre le très talentueux Tony Morris ici : https://groups.google.com/d/topic/scala-user/_n9Lh-a2PyQ/discussion