Ruby Language Application partielle et currying


Exemple

Techniquement, Ruby n'a pas de fonctions, mais des méthodes. Cependant, une méthode Ruby se comporte presque de manière identique aux fonctions dans un autre langage:

def double(n)
  n * 2
end

Cette méthode / fonction normale prend un paramètre n , le double et renvoie la valeur. Définissons maintenant une fonction d'ordre supérieur (ou méthode):

def triple(n)
  lambda {3 * n}
end

Au lieu de renvoyer un nombre, triple renvoie une méthode. Vous pouvez le tester à l'aide d' Interactive Ruby Shell :

$ irb --simple-prompt
>> def double(n)
>>   n * 2
>> end
=> :double
>> def triple(n)
>>   lambda {3 * n}
>> end
=> :triple
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x007fd07f07bdc0@(irb):7 (lambda)>

Si vous voulez réellement obtenir le nombre triplé, vous devez appeler (ou "réduire") le lambda:

triple_two = triple(2)
triple_two.call # => 6

Ou plus concis:

triple(2).call

Currying et applications partielles

Cela n'est pas utile pour définir des fonctionnalités très basiques, mais cela est utile si vous voulez avoir des méthodes / fonctions qui ne sont pas appelées ou réduites instantanément. Par exemple, supposons que vous souhaitiez définir des méthodes qui ajoutent un nombre à un nombre spécifique (par exemple, add_one(2) = 3 ). Si vous deviez en définir une tonne, vous pourriez faire:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

Cependant, vous pourriez aussi faire ceci:

add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)

En utilisant le lambda calcul, on peut dire que add est (λa.(λb.(a+b))) . Le curry est une façon d' appliquer partiellement add . Donc add.curry.(1) , est (λa.(λb.(a+b)))(1) qui peut être réduit à (λb.(1+b)) . L'application partielle signifie que nous avons passé un argument à add mais que l'autre argument a été fourni ultérieurement. La sortie est une méthode spécialisée.

Des exemples plus utiles de currying

Disons que nous avons une très grande formule générale, que si nous spécifions certains arguments, nous pouvons en tirer des formules spécifiques. Considérez cette formule:

f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)

Cette formule est conçue pour fonctionner en trois dimensions, mais supposons que nous ne souhaitons que cette formule en ce qui concerne y et z. Disons aussi que pour ignorer x, nous voulons lui donner la valeur pi / 2. Faisons d'abord la formule générale:

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}

Maintenant, utilisons le curry pour obtenir notre formule yz :

f_yz = f.curry.(Math::PI/2)

Ensuite, appeler le lambda stocké dans f_yz :

f_xy.call(some_value_x, some_value_y)

C'est assez simple, mais disons que nous voulons obtenir la formule pour xz . Comment pouvons-nous régler y Math::PI/2 si ce n'est pas le dernier argument? Eh bien, c'est un peu plus compliqué:

f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}

Dans ce cas, nous devons fournir des espaces réservés pour le paramètre que nous ne pré-remplissons pas. Pour plus de cohérence, nous pourrions écrire f_xy comme ceci:

f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}

Voici comment fonctionne le calcul lambda pour f_yz :

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_yz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (π/2) # Reduce => 
f_yz = (λy.(λz.(sin((π/2)*y) * sin(y*z) * sin(z*(π/2))))

Maintenant, regardons f_xz

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_xz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (λt.t) (π/2)  # Reduce =>
f_xz = (λt.(λz.(sin(t*(π/2)) * sin((π/2)*z) * sin(z*t))))

Pour plus d'informations sur le lambda, essayez ceci .