Suche…


Syntax

  • Proc.new ( Block )
  • Lambda {| args | code}
  • -> (arg1, arg2) {code}
  • object.to_proc
  • {| single_arg | code}
  • do | arg, (Schlüssel, Wert) | Code- Ende

Bemerkungen

Seien Sie vorsichtig mit der Rangfolge der Operatoren, wenn eine Zeile mit mehreren Methoden verkettet ist, z.

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

Anstatt so etwas wie der Druck abCDeFg , wie man erwarten würde, gibt es so etwas wie #<Enumerator:0x00000000af42b28> - denn do ... end hat niedrigere Priorität als Verfahren, was bedeutet , dass gsub sieht nur die /./ Argument und nicht das Blockargument. Es wird ein Enumerator zurückgegeben. Der Block endet bestanden puts , die es ignoriert und zeigt nur das Ergebnis der gsub(/./) .

Um dies zu beheben, wickeln Sie den gsub Aufruf entweder in Klammern ein oder verwenden Sie stattdessen { ... } .

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"

Wir haben die Methode call_the_block aus dem letzten Beispiel kopiert. Hier können Sie sehen, dass ein proc erstellt wird, indem Sie die proc Methode mit einem Block aufrufen. Sie können auch sehen, dass Blöcke wie Methoden implizite Rückgaben haben, was auch für procs (und lambdas) gilt. In der Definition von its_a können Sie sehen, dass Blöcke sowohl Splat-Argumente als auch normale Argumente annehmen können. sie sind auch in der Lage Standardargumente zu nehmen, aber ich kann nicht einen Weg finden , dass die Arbeit in Schließlich können Sie sehen , dass es möglich ist , mehrere Syntaxen zu verwenden , um ein Verfahren zu nennen -. entweder die call - Methode oder die [] Operator.

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)

Sie können auch -> zum Erstellen und .() Zum Aufrufen von Lambda verwenden

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

Hier kann man sehen, dass ein Lambda fast dasselbe ist wie ein Proc. Es gibt jedoch einige Vorbehalte:

  • Die Argumente eines Lambda werden durchgesetzt; Wenn Sie die falsche Anzahl von Argumenten an ein Lambda übergeben, wird ein ArgumentError . Sie können immer noch Standardparameter, Splat-Parameter usw. haben.

  • return aus einem Lambda kehrt aus dem Lambda zurück, während return aus einem proc aus dem einschließenden Bereich zurückkommt

    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"
    

Objekte als Blockargumente für Methoden

Wenn Sie ein & (kaufmännisches Und) vor ein Argument setzen, wird es als Block der Methode übergeben. Objekte werden mit der to_proc Methode in einen Proc 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)

Dies ist ein übliches Muster in Ruby und wird von vielen Standardklassen bereitgestellt.

Beispiel: Symbol s implementiert to_proc indem es sich selbst an das Argument sendet:

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

Dies Enumerable das nützliche &:symbol Idiom, das häufig bei Enumerable Objekten verwendet wird:

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

Blöcke

Blöcke sind Code-Blöcke zwischen Klammern {} (normalerweise für einzeilige Blöcke) oder do..end (werden für mehrzeilige Blöcke verwendet).

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

Hinweis: geschweifte Klammern haben eine höhere Priorität als do..end

Nachgeben

Blöcke können innerhalb von Methoden und Funktionen mit dem Wort 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

Seien Sie jedoch vorsichtig, wenn yield ohne Block aufgerufen wird, wird ein LocalJumpError . Zu diesem Zweck bietet Ruby eine andere Methode namens block_given? Damit können Sie prüfen, ob ein Block vor dem Aufruf von Yield übergeben wurde

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 kann dem Block auch Argumente bieten

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

Obwohl dies ein einfaches Beispiel ist yield kann das Nachschlagen sehr nützlich sein, um den direkten Zugriff auf Instanzvariablen oder Auswertungen im Kontext eines anderen Objekts zu ermöglichen. Zum Beispiel:

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>

Wie Sie sehen können, macht die Verwendung von yield auf diese Weise den Code lesbarer als das kontinuierliche Aufrufen von app.configuration.#method_name . Stattdessen können Sie die gesamte Konfiguration innerhalb des Blocks durchführen, wobei der Code enthalten bleibt.

Variablen

Variablen für Blöcke sind lokal im Block (ähnlich den Variablen von Funktionen). Sie sterben, wenn der Block ausgeführt wird.

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

Blöcke können nicht gespeichert werden, sie sterben einmal ausgeführt. Um Blöcke zu speichern, müssen Sie procs und lambdas .

Umwandlung in Proc

Objekte, die auf to_proc reagieren, können mit dem Operator & in to_proc konvertiert werden (wodurch sie auch als Blöcke übergeben werden).

Die Klasse Symbol definiert #to_proc so dass versucht wird, die entsprechende Methode für das als Parameter empfangene Objekt aufzurufen.

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

Methodenobjekte definieren auch #to_proc .

output = method( :p )

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

Teilanwendung und Currying

Technisch hat Ruby keine Funktionen, sondern Methoden. Eine Ruby-Methode verhält sich jedoch fast identisch zu Funktionen in anderen Sprachen:

def double(n)
  n * 2
end

Diese normale Methode / Funktion nimmt einen Parameter n , verdoppelt diesen und gibt den Wert zurück. Nun definieren wir eine Funktion (oder Methode) höherer Ordnung:

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

Anstatt eine Zahl zurückzugeben, gibt triple eine Methode zurück. Sie können es mit der Interactive Ruby Shell testen:

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

Wenn Sie tatsächlich die verdreifachte Nummer erhalten möchten, müssen Sie das Lambda anrufen (oder "reduzieren"):

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

Oder genauer:

triple(2).call

Currying und Teilanwendungen

Dies ist nicht nützlich, um sehr grundlegende Funktionen zu definieren, aber es ist nützlich, wenn Sie Methoden / Funktionen haben möchten, die nicht sofort aufgerufen oder reduziert werden. add_one(2) = 3 , Sie möchten Methoden definieren, die eine Nummer durch eine bestimmte Nummer hinzufügen (zum Beispiel add_one(2) = 3 ). Wenn Sie eine Tonne davon definieren müssten, könnten Sie Folgendes tun:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

Sie können dies jedoch auch tun:

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

Mit dem Lambda-Kalkül können wir sagen, dass add (λa.(λb.(a+b))) . Currying ist eine Möglichkeit , add teilweise anzuwenden . So add.curry.(1) , (λa.(λb.(a+b)))(1) das auf (λb.(1+b)) reduziert werden kann. Partielle Anwendung bedeutet , dass wir ein Argument übergeben add , aber das andere Argument links später geliefert werden. Die Ausgabe ist eine spezialisierte Methode.

Weitere nützliche Beispiele für das Currying

Nehmen wir an, wir haben eine sehr große allgemeine Formel: Wenn wir bestimmte Argumente angeben, können wir bestimmte Formeln daraus erhalten. Betrachten Sie diese Formel:

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

Diese Formel ist für das Arbeiten in drei Dimensionen gedacht, aber nehmen wir an, wir wollen diese Formel nur in Bezug auf y und z. Sagen wir auch, dass wir den Wert auf pi / 2 setzen, um x zu ignorieren. Lassen Sie uns zuerst die allgemeine Formel machen:

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

Verwenden wir jetzt Currying, um unsere yz Formel zu erhalten:

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

Dann rufen Sie das in f_yz gespeicherte Lambda auf:

f_xy.call(some_value_x, some_value_y)

Das ist ziemlich einfach, aber nehmen wir an, wir wollen die Formel für xz . Wie können wir y auf Math::PI/2 wenn es nicht das letzte Argument ist? Nun, es ist etwas komplizierter:

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

In diesem Fall müssen wir Platzhalter für den Parameter angeben, den wir nicht vorab füllen. Für Konsistenz könnten wir f_xy so schreiben:

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

So funktioniert der Lambda-Kalkül für 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))))

Nun sehen wir uns 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))))

Weitere Informationen zum Lambda-Kalkül finden Sie hier .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow