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, medan return 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 .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow