Que serait une construction équivalente d'une monade en Ruby?
Réponses
Trop de publicités?Les précis de la définition technique: Une monade, en Ruby, serait une classe avec des bind
et self.unit
méthodes définis pour toutes les instances m:
m.class.unit[a].bind[f] == f[a]
m.bind[m.class.unit] == m
m.bind[f].bind[g] == m.bind[lambda {|x| f[x].bind[g]}]
Quelques exemples pratiques
Un exemple très simple d'une monade est le paresseux Identité monade, qui émule paresseux de la sémantique dans le Ruby (un strict de la langue):
class Id
def initialize(lam)
@v = lam
end
def force
@v[]
end
def self.unit
lambda {|x| Id.new(lambda { x })}
end
def bind
x = self
lambda {|f| Id.new(lambda { f[x.force] })}
end
end
En utilisant cela, vous pouvez enchaîner les procs ensemble dans un paresseux manière. Par exemple, dans la suite, x
est un conteneur "contenant" 40
, mais le calcul n'est pas effectué jusqu'à la deuxième ligne, en témoigne le fait que l' puts
déclaration n'a pas de sortie à rien jusqu' force
s'appelle:
x = Id.new(lambda {20}).bind[lambda {|x| puts x; Id.unit[x * 2]}]
x.force
Un peu le même, moins abstraite exemple serait une monade pour obtenir des valeurs d'une base de données. Nous allons supposer que nous avons une classe Query
avec un run(c)
méthode qui prend une connexion de base de données c
, et un constructeur de Query
objets qui prend, disons, une chaîne SQL. Donc, DatabaseValue
représente une valeur qui est à venir à partir de la base de données. DatabaseValue est une monade:
class DatabaseValue
def initialize(lam)
@cont = lam
end
def self.fromQuery(q)
DatabaseValue.new(lambda {|c| q.run(c) })
end
def run(c)
@cont[c]
end
def self.unit
lambda {|x| DatabaseValue.new(lambda {|c| x })}
end
def bind
x = self
lambda {|f| DatabaseValue.new(lambda {|c| f[x.run(c)].run(c) })}
end
end
Cela vous permettrait de la chaîne d'appels de base de données via une connexion unique, comme suit:
q = unit["John"].bind[lambda {|n|
fromQuery(Query.new("select dep_id from emp where name = #{n}")).
bind[lambda {|id|
fromQuery(Query.new("select name from dep where id = #{id}"))}].
bind[lambda { |name| unit[doSomethingWithDeptName(name)] }]
begin
c = openDbConnection
someResult = q.run(c)
rescue
puts "Error #{$!}"
ensure
c.close
end
OK, alors pourquoi sur terre serait vous faire? Parce qu'il y a extrêmement de fonctions utiles qui peut-être écrit une fois pour toutes les monades. Donc le code que vous le feriez normalement écrire encore et peut être réutilisé pour toute monade une fois que vous avez simplement à mettre en oeuvre unit
et bind
. Par exemple, on peut définir une Monade mixin qui donne à toutes ces classes avec quelques méthodes utiles:
module Monad
I = lambda {|x| x }
# Structure-preserving transform that applies the given function
# across the monad environment.
def map
lambda {|f| bind[lambda {|x| self.class.unit[f[x]] }]}
end
# Joins a monad environment containing another into one environment.
def flatten
bind[I]
end
# Applies a function internally in the monad.
def ap
lambda {|x| liftM2[I,x] }
end
# Binds a binary function across two environments.
def liftM2
lambda {|f, m|
bind[lambda {|x1|
m.bind[lambda {|x2|
self.class.unit[f[x1,x2]]
}]
}]
}
end
end
Et cela nous permet de faire encore plus de choses utiles, comme de définir cette fonction:
# An internal array iterator [m a] => m [a]
def sequence(m)
snoc = lambda {|xs, x| xs + [x]}
lambda {|ms| ms.inject(m.unit[[]], &(lambda {|x, xs| x.liftM2[snoc, xs] }))}
end
L' sequence
méthode de classe qui mêle dans la Monade, et retourne une fonction qui prend un tableau de monadique valeurs et la transforme en une valeur monadique contenant un tableau. Ils pourraient être Id
valeurs (tournant d'un tableau d'Identités dans une Identité contenant un tableau), ou DatabaseValue
objets (tournant d'un tableau de requêtes dans une requête qui retourne un tableau), ou des fonctions (en tournant à un éventail de fonctions dans une fonction qui retourne un tableau), ou des tableaux (tournant d'un tableau de tableaux à l'intérieur-out), ou les analyseurs, les continuations, les machines d'état, ou tout ce qui pourrait éventuellement mélanger dans l' Monad
module (qui, comme il s'avère, est vrai pour presque toutes les structures de données).
Pour ajouter mon grain de sel, je dirais que hzap a mal compris le concept de monades. Ce n'est pas seulement de type « interface » ou une « structure en fournissant des fonctions », c'est de la boue plus que cela. C'est une structure abstraite des opérations d'apport de bind (( > > = ) et l'unité (retour)) qui suivent, comme Ken et Apocalisp dit, des règles strictes.
Si vous êtes intéressé par les monades et souhaitez en savoir plus sur eux plus que les quelques choses dans ces réponses, je vous conseille fortement de lire : les Monades pour la programmation fonctionnelle (pdf), par Wadler.
Te voir!
PS: je vois que je ne pas répondre directement à votre question, mais Apocalisp a déjà fait, et je pense (du moins l'espère) que mes précisions ont été la peine
Les monades ne sont pas des constructions de langage. Ce sont juste des types qui implémentent une interface particulière, et comme Ruby est typé de manière dynamique, toute classe qui implémente quelque chose comme collect
dans les tableaux, une méthode de jointure (comme flatten
mais en aplatit une seule. niveau), et un constructeur qui peut tout envelopper, est une monade.
La suite sur les réponses ci-dessus:
Vous pouvez être intéressé à vérifier Rumonade, un rubis gemme qui met en œuvre une Monade mix-in pour le Rubis.
Romande est mis en œuvre comme mix, et attendre son hôte de la classe pour mettre en œuvre les méthodes d' self.unit
et #bind
(et, éventuellement, self.empty
), et fera le reste pour faire fonctionner les choses pour vous.
Vous pouvez l'utiliser pour map
sur Option
, que vous êtes habitué à la Scala, et vous pouvez même obtenir quelques belles multiples-non-valeurs de retour de validations, à la Scalaz de Validation de classe.