Szukaj…


Składnia

  • Proc. Nowa ( blok )
  • lambda {| args | kod }
  • -> (arg1, arg2) {kod}
  • object.to_proc
  • {| single_arg | kod }
  • do | arg, (klucz, wartość) | koniec kodu

Uwagi

Uważaj na pierwszeństwo operatora, gdy masz linię z wieloma metodami, na przykład:

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

Zamiast abCDeFg coś takiego jak abCDeFg , jak można się spodziewać, wypisuje coś takiego: #<Enumerator:0x00000000af42b28> - to dlatego do ... end że do ... end ma niższy priorytet niż metody, co oznacza, że gsub widzi tylko argument /./ , a nie argument blokowy. Zwraca moduł wyliczający. Blok jest ostatecznie przekazywany do puts , co go ignoruje i wyświetla wynik gsub(/./) .

Aby to naprawić, zawiń wywołanie gsub w nawiasach lub zamiast tego użyj { ... } .

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"

Skopiowaliśmy metodę call_the_block z ostatniego przykładu. Tutaj możesz zobaczyć, że proc jest tworzony przez wywołanie metody proc z blokiem. Możesz także zobaczyć, że bloki, podobnie jak metody, mają niejawne zwroty, co oznacza, że procs (i lambdas) również. W definicji its_a widać, że bloki mogą przyjmować argumenty splat, jak również zwykłe; są również zdolne do przyjmowania domyślnych argumentów, ale nie mogłem wymyślić sposobu, aby to wykorzystać. Na koniec widać, że można użyć wielu składni do wywołania metody - albo metody call , albo [] 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)

Możesz także użyć -> aby utworzyć i .() Aby wywołać 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

Tutaj możesz zobaczyć, że lambda jest prawie taka sama jak proc. Istnieje jednak kilka zastrzeżeń:

  • Egzekwowanie argumentów lambdy jest egzekwowane; przekazanie niewłaściwej liczby argumentów do lambda spowoduje podniesienie ArgumentError . Nadal mogą mieć parametry domyślne, parametry splat itp.

  • return z wewnątrz lambda zwraca z lambda, podczas gdy return z proc zwraca z zakresu obejmującego:

    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"
    

Obiekty jako argumenty blokowe dla metod

Umieszczenie znaku & (ampersand) przed argumentem przekaże go jako blok metody. Przedmioty zostaną przekształcone do Proc pomocą to_proc metody.

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

greet = Greeter.new

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

Jest to powszechny wzorzec w Ruby i zapewnia go wiele standardowych klas.

Na przykład Symbol s implementuje to_proc , wysyłając się do argumentu:

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

Umożliwia to użyteczny idiom &:symbol Enumerable często używany w obiektach Enumerable :

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

Bloki

Bloki to fragmenty kodu zamknięte między nawiasami klamrowymi {} (zwykle dla bloków do..end ) lub do..end (używane dla bloków wieloliniowych).

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

Uwaga: nawiasy klamrowe mają wyższy priorytet niż do..end

Wydajność

Bloki mogą być używane w metodach i funkcjach za pomocą słowa 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

Bądź jednak ostrożny, jeśli yield jest nazywany bez bloku to podniesie się LocalJumpError . W tym celu Ruby udostępnia inną metodę o nazwie block_given? pozwala to sprawdzić, czy blok został przekazany przed wywołaniem dochodu

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 może również dostarczać argumenty do bloku

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

Chociaż jest to prosty przykład, yield może być bardzo przydatne do umożliwienia bezpośredniego dostępu do zmiennych instancji lub ocen wewnątrz kontekstu innego obiektu. Na przykład:

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>

Jak widać używając yield w ten sposób sprawia, że kod jest bardziej czytelny niż nieustannie dzwoni app.configuration.#method_name . Zamiast tego możesz wykonać całą konfigurację wewnątrz bloku, zachowując kod.

Zmienne

Zmienne dla bloków są lokalne dla bloku (podobne do zmiennych funkcji), umierają, gdy blok jest wykonywany.

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

Bloki nie mogą być uratowane, giną po wykonaniu. Aby zapisać bloki, musisz użyć procs i lambdas .

Konwertowanie na Proc

Obiekty, które odpowiadają na to_proc można przekonwertować na procs za pomocą operatora & (co pozwoli również na przekazywanie ich jako bloków).

Symbol klasy definiuje #to_proc więc próbuje wywołać odpowiednią metodę na obiekcie, który otrzymuje jako parametr.

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

Obiekty metod definiują również #to_proc .

output = method( :p )

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

Częściowe zastosowanie i curry

Technicznie, Ruby nie ma funkcji, ale metody. Jednak metoda Ruby zachowuje się prawie identycznie jak funkcje w innym języku:

def double(n)
  n * 2
end

Ta normalna metoda / funkcja przyjmuje parametr n , podwaja go i zwraca wartość. Teraz zdefiniujmy funkcję (lub metodę) wyższego rzędu:

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

Zamiast zwracać liczbę, triple zwraca metodę. Możesz to przetestować za pomocą interaktywnej powłoki Ruby :

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

Jeśli chcesz faktycznie uzyskać trzykrotny numer, musisz wywołać (lub „zmniejszyć”) lambda:

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

Lub bardziej zwięźle:

triple(2).call

Curry i częściowe zastosowania

Nie jest to przydatne w kontekście definiowania bardzo podstawowej funkcjonalności, ale jest przydatne, jeśli chcesz mieć metody / funkcje, które nie są natychmiast wywoływane lub redukowane. Załóżmy na przykład, że chcesz zdefiniować metody, które dodają liczbę według określonej liczby (na przykład add_one(2) = 3 ). Gdybyś musiał zdefiniować tonę tych, możesz zrobić:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

Możesz jednak również to zrobić:

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

Za pomocą rachunku lambda możemy powiedzieć, że add to (λa.(λb.(a+b))) . Curry to sposób na częściowe zastosowanie add . Więc add.curry.(1) , to (λa.(λb.(a+b)))(1) które można sprowadzić do (λb.(1+b)) . Częściowa aplikacja oznacza, że przekazaliśmy jeden argument do add ale pozostawiliśmy drugi argument do podania później. Wynik jest metodą specjalistyczną.

Bardziej przydatne przykłady curry

Powiedzmy, że mamy naprawdę dużą ogólną formułę, że jeśli podamy jej określone argumenty, możemy uzyskać z niej określone formuły. Rozważ tę formułę:

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

Ta formuła jest przeznaczona do pracy w trzech wymiarach, ale powiedzmy, że chcemy tej formuły tylko w odniesieniu do yi z. Powiedzmy również, że aby zignorować x, chcemy ustawić jego wartość na pi / 2. Najpierw utwórzmy ogólną formułę:

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

Teraz użyjmy curry, aby uzyskać naszą formułę yz :

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

Następnie, aby wywołać lambda przechowywaną w f_yz :

f_xy.call(some_value_x, some_value_y)

Jest to dość proste, ale powiedzmy, że chcemy uzyskać wzór na xz . Jak ustawić y na Math::PI/2 jeśli nie jest to ostatni argument? Cóż, jest to trochę bardziej skomplikowane:

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

W takim przypadku musimy podać symbole zastępcze dla parametru, który nie jest wstępnie wypełniany. Dla spójności możemy napisać f_xy następujący sposób:

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

Oto jak działa rachunek lambda dla 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))))

Teraz spójrzmy na 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))))

Aby uzyskać więcej informacji na temat rachunku lambda, spróbuj tego .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow