Recherche…


Syntaxe

  • Proc.new ( bloc )
  • lambda {| args | code}
  • -> (arg1, arg2) {code}
  • object.to_proc
  • {| single_arg | code}
  • do | arg, (clé, valeur) | fin du code

Remarques

Faites attention à la priorité des opérateurs lorsque vous avez une ligne avec plusieurs méthodes enchaînées, comme:

str = "abcdefg"
puts str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end

Au lieu d'imprimer quelque chose comme abCDeFg , comme on pouvait s'y attendre, il imprime quelque chose comme #<Enumerator:0x00000000af42b28> - c'est parce que do ... end a priorité moindre que les méthodes, ce qui signifie que gsub ne voit que l' /./ arguments , et non l'argument de bloc. Il retourne un énumérateur. Le bloc finit par passer à puts , qui l'ignore et affiche simplement le résultat de gsub(/./) .

Pour résoudre ce problème, gsub appel gsub entre parenthèses ou utilisez plutôt { ... } .

Proc

def call_the_block(&calling); calling.call; end

its_a = proc do |*args|
  puts "It's a..." unless args.empty?
  "beautiful day"
end

puts its_a       #=> "beautiful day"
puts its_a.call  #=> "beautiful day"
puts its_a[1, 2] #=> "It's a..." "beautiful day"

Nous avons copié la méthode call_the_block du dernier exemple. Ici, vous pouvez voir qu'un processus est fait en appelant la méthode proc avec un bloc. Vous pouvez également voir que les blocs, comme les méthodes, ont des retours implicites, ce qui signifie que procs (et lambdas) le font aussi. Dans la définition de its_a , vous pouvez voir que les blocs peuvent prendre des arguments splat aussi bien que des arguments normaux; ils sont également capables de prendre des arguments par défaut, mais je ne pouvais pas penser à un moyen de travailler avec. Enfin, vous pouvez voir qu'il est possible d'utiliser plusieurs syntaxes pour appeler une méthode - soit la méthode d' call , soit le [] opérateur

Lambdas

# lambda using the arrow syntax
hello_world = -> { 'Hello World!' }
hello_world[]
# 'Hello World!'

# lambda using the arrow syntax accepting 1 argument
hello_world = ->(name) { "Hello #{name}!" }
hello_world['Sven']
# "Hello Sven!"

the_thing = lambda do |magic, ohai, dere|
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
end

the_thing.call(1, 2, 3)
# magic! 1
# ohai 3
# 2 means hello

the_thing.call(1, 2)
# ArgumentError: wrong number of arguments (2 for 3)

the_thing[1, 2, 3, 4]
# ArgumentError: wrong number of arguments (4 for 3)

Vous pouvez également utiliser -> pour créer et .() Pour appeler lambda

the_thing = ->(magic, ohai, dere) {
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
}

the_thing.(1, 2, 3)
# => magic! 1
# => ohai 3
# => 2 means hello

Ici, vous pouvez voir qu'un lambda est presque identique à un proc. Cependant, il y a plusieurs mises en garde:

  • L'arité des arguments d'un lambda est appliquée; transmettre le nombre d'arguments incorrect à un lambda, déclenche une erreur ArgumentError . Ils peuvent toujours avoir des paramètres par défaut, des paramètres de splat, etc.

  • return depuis un lambda retourne du lambda, tout en return d'un proc retourne hors de la portée englobante:

    def try_proc
      x = Proc.new {
        return # Return from try_proc
      }
      x.call
      puts "After x.call" # this line is never reached
    end
    
    def try_lambda
      y = -> {
        return # return from y
      }
      y.call
      puts "After y.call" # this line is not skipped
    end
    
    try_proc # No output
    try_lambda # Outputs "After y.call"
    

Objets en tant qu'arguments de bloc aux méthodes

Mettre un & (esperluette) devant un argument le transmettra comme bloc de la méthode. Les objets seront convertis en un Proc utilisant la méthode to_proc .

class Greeter
  def to_proc
    Proc.new do |item|
      puts "Hello, #{item}"
    end
  end
end

greet = Greeter.new

%w(world life).each(&greet)

Ceci est un modèle courant dans Ruby et de nombreuses classes standard le fournissent.

Par exemple, Symbol implémente to_proc en s'envoyant à l'argument:

# Example implementation
class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

Cela active l'idiome utile &:symbol , couramment utilisé avec les objets Enumerable :

letter_counts = %w(just some words).map(&:length)  # [4, 4, 5]

Blocs

Les blocs sont des morceaux de code entre accolades {} (généralement pour les blocs à une seule ligne) ou do..end (utilisés pour les blocs à plusieurs lignes).

5.times { puts "Hello world" } # recommended style for single line blocks

5.times do
    print "Hello "
    puts "world"
end   # recommended style for multi-line blocks

5.times {
    print "hello "
    puts "world" } # does not throw an error but is not recommended

Remarque: les accolades ont une priorité plus élevée que do..end

Céder

Les blocs peuvent être utilisés dans les méthodes et les fonctions en utilisant le mot yield :

def block_caller
    puts "some code"
    yield
    puts "other code"
end
block_caller { puts "My own block" } # the block is passed as an argument to the method.
#some code
#My own block
#other code

Attention, si yield est appelé sans bloc, il LocalJumpError une LocalJumpError . block_given? fournit à cette fin une autre méthode appelée block_given? cela vous permet de vérifier si un bloc a été passé avant d'appeler le rendement

def block_caller
  puts "some code" 
  if block_given? 
    yield
  else
    puts "default"
  end
  puts "other code"
end
block_caller 
# some code
# default
# other code
block_caller { puts "not defaulted"}
# some code
# not defaulted
# other code

yield peut également fournir des arguments au bloc

def yield_n(n)
  p = yield n if block_given?
  p || n 
end
yield_n(12) {|n| n + 7 } 
#=> 19 
yield_n(4) 
#=> 4

Bien qu'il s'agisse d'un exemple simple, le yield peut être très utile pour permettre un accès direct aux variables d'instance ou aux évaluations dans le contexte d'un autre objet. Par exemple:

class Application
  def configuration
    @configuration ||= Configuration.new
    block_given? ? yield(@configuration) : @configuration
  end
end
class Configuration; end

app = Application.new 
app.configuration do |config| 
  puts config.class.name
end
# Configuration
#=> nil 
app.configuration
#=> #<Configuration:0x2bf1d30>

Comme vous pouvez le constater, l'utilisation de yield de cette manière rend le code plus lisible que l'appel continu de app.configuration.#method_name . Au lieu de cela, vous pouvez effectuer toute la configuration à l'intérieur du bloc en conservant le code contenu.

Les variables

Les variables pour les blocs sont locales au bloc (similaire aux variables des fonctions), elles meurent lorsque le bloc est exécuté.

my_variable = 8
3.times do |x|
    my_variable = x 
    puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8

Les blocs ne peuvent pas être enregistrés, ils meurent une fois exécutés. Pour enregistrer des blocs, vous devez utiliser procs et lambdas .

Conversion en Proc

Les objets qui répondent à to_proc peuvent être convertis en procs avec l'opérateur & (qui leur permettra également d'être transmis en tant que blocs).

La classe Symbol définit #to_proc afin qu'elle tente d'appeler la méthode correspondante sur l'objet qu'elle reçoit en paramètre.

p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]

Les objets méthode définissent également #to_proc .

output = method( :p )

[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"

Application partielle et currying

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 .



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow