Ruby Language
Blöcke und Procs und Lambdas
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ährendreturn
aus einem proc aus dem einschließenden Bereich zurückkommtdef 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 .