Ruby Language
Block och Procs och Lambdas
Sök…
Syntax
- Proc.new ( block )
- lambda {| args | kod}
- -> (arg1, arg2) {kod}
- object.to_proc
- {| singel_arg | kod}
- do | arg, (nyckel, värde) | kod slut
Anmärkningar
Var försiktig med operatörens företräde när du har en linje med flera kedjor, som:
str = "abcdefg"
puts str.gsub(/./) do |match|
rand(2).zero? ? match.upcase : match.downcase
end
Istället för att skriva ut något som abCDeFg
, som du kan förvänta dig, skriver det ut något som #<Enumerator:0x00000000af42b28>
- detta beror på att do ... end
har lägre prioritet än metoder, vilket innebär att gsub
bara ser /./
, och inte blockargumentet. Det returnerar en uppräknare. Blocket hamnar vidare till puts
, vilket ignorerar det och bara visar resultatet av gsub(/./)
.
För att fixa detta, antingen gsub
samtalet inom parentes eller använd { ... }
istället.
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"
Vi har kopierat metoden call_the_block
från det senaste exemplet. Här kan du se att en proc görs genom att kalla proc
metoden med ett block. Du kan också se att block, som metoder, har implicit avkastning, vilket innebär att procs (och lambdas) också gör. I definitionen av its_a
kan du se att block kan ta splattargument och normala; de kan också ta standardargument, men jag kunde inte tänka på ett sätt att arbeta det i. Slutligen kan du se att det är möjligt att använda flera syntaxer för att ringa en metod - antingen call
eller []
operatör.
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)
Du kan också använda ->
att skapa och .()
För att ringa 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
Här kan du se att en lambda är nästan densamma som en proc. Det finns dock flera varningar:
Arten av en lambdas argument upprätthålls; överföring av fel antal argument till en lambda, kommer att höja en
ArgumentError
. De kan fortfarande ha standardparametrar, splitparametrar etc.return
inifrån en lambda återvänder från lambda, medanreturn
från en proc återvänder från det inneslutande omfånget: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"
Objekt som blockargument till metoder
Att lägga en &
(ampersand) framför ett argument kommer att passera det som metodens block. Objekt konverteras till en Proc
med metoden 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)
Detta är ett vanligt mönster i Ruby och många standardklasser ger det.
Till exempel, Symbol
s genomföra to_proc
genom att skicka sig argumentet:
# Example implementation
class Symbol
def to_proc
Proc.new do |receiver|
receiver.send self
end
end
end
Detta möjliggör användbart &:symbol
symbolidiom, som vanligtvis används med Enumerable
objekt:
letter_counts = %w(just some words).map(&:length) # [4, 4, 5]
Blocks
Block är bitar av kod som är slutna mellan hängslen {}
(vanligtvis för block med en linje) eller do..end
(används för block med flera linjer).
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
OBS: hängslen har högre prioritet än gör do..end
vilket ger
Block kan användas inuti metoder och funktioner med 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
Var försiktig men om yield
kallas utan ett block kommer det att höja en LocalJumpError
. För detta ändamål ger rubin en annan metod som kallas block_given?
detta gör att du kan kontrollera om ett block har passerat innan du ringer avkastning
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
kan erbjuda argument till blocket också
def yield_n(n)
p = yield n if block_given?
p || n
end
yield_n(12) {|n| n + 7 }
#=> 19
yield_n(4)
#=> 4
Medan detta är ett enkelt exempel yield
ning kan vara mycket användbara för att direkt nå instansvariabler eller utvärderingar inuti ramen för ett annat objekt. Till exempel:
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>
Som du ser att använda yield
på detta sätt gör koden mer läsbar än att kontinuerligt ringa app.configuration.#method_name
. Istället kan du utföra all konfiguration i blocket och behålla koden.
variabler
Variabler för block är lokala för blocket (liknande funktionernas variabler), de dör när blocket körs.
my_variable = 8
3.times do |x|
my_variable = x
puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8
Block kan inte sparas, de dör när de körts. För att spara block måste du använda procs
och lambdas
.
Konvertera till Proc
Objekt som svarar på to_proc
kan konverteras till procs med &
operatören (vilket också gör att de kan skickas som block).
Klassen Symbol definierar #to_proc
så den försöker ringa motsvarande metod på det objekt som den får som parameter.
p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]
#to_proc
definierar också #to_proc
.
output = method( :p )
[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"
Partiell applikation och currying
Tekniskt sett har Ruby inte funktioner utan metoder. Men en Ruby-metod uppträder nästan identiskt med funktioner på andra språk:
def double(n)
n * 2
end
Denna normala metod / funktion tar en parameter n
, fördubblar den och returnerar värdet. Låt oss nu definiera en funktion med högre ordning (eller metod):
def triple(n)
lambda {3 * n}
end
I stället för att returnera ett nummer returnerar triple
en metod. Du kan testa det med 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)>
Om du faktiskt vill få det tredubbla numret måste du ringa (eller "minska") lambda:
triple_two = triple(2)
triple_two.call # => 6
Eller mer kortfattat:
triple(2).call
Currying och delvis applikation
Detta är inte användbart när det gäller att definiera mycket grundläggande funktioner, men det är användbart om du vill ha metoder / funktioner som inte direkt kallas eller reduceras. Låt oss till exempel säga att du vill definiera metoder som lägger till ett nummer med ett specifikt nummer (till exempel add_one(2) = 3
). Om du var tvungen att definiera massor av dessa kan du göra:
def add_one(n)
n + 1
end
def add_two(n)
n + 2
end
Men du kan också göra detta:
add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)
Med lambda-beräkningen kan vi säga att add
är (λa.(λb.(a+b)))
. Currying är ett sätt att delvis applicera add
. Så add.curry.(1)
, är (λa.(λb.(a+b)))(1)
som kan reduceras till (λb.(1+b))
. Partiell applikation innebär att vi gick över ett argument för att add
men lämnade det andra argumentet att levereras senare. Utgången är en specialiserad metod.
Mer användbara exempel på currying
Låt oss säga att vi har riktigt stor allmän formel, att om vi specificerar vissa argument för det, kan vi få specifika formler från den. Tänk på denna formel:
f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)
Denna formel är gjord för att arbeta i tre dimensioner, men låt oss säga att vi bara vill ha den här formeln när det gäller y och z. Låt oss också säga att för att ignorera x, vill vi ställa in det till pi / 2. Låt oss först skapa den allmänna formeln:
f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}
Låt oss nu använda currying för att få vår yz
formel:
f_yz = f.curry.(Math::PI/2)
För att ringa lambda lagrad i f_yz
:
f_xy.call(some_value_x, some_value_y)
Detta är ganska enkelt, men låt oss säga att vi vill få formeln för xz
. Hur kan vi ställa y
till Math::PI/2
om det inte är det sista argumentet? Det är lite mer komplicerat:
f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}
I det här fallet måste vi tillhandahålla platshållare för den parameter vi inte fyller i. För konsistens kan vi skriva f_xy
så här:
f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}
Så här fungerar lambda-beräkningen 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))))
Låt oss nu titta på 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))))
För mer läsning om lambdakalkylen kan du prova detta .