39 votes

Monade équivalent en rubis

Que serait une construction équivalente d'une monade en Ruby?

74voto

Apocalisp Points 22526

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).

6voto

rks Points 762

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

5voto

hzap Points 988

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.

1voto

ms-tg Points 1087

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.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X