Any fool can make things bigger, more complex, and more violent. It takes a touch of genius and a lot of courage to move in the opposite direction. Albert Einstein

o Ruby

Nowości i zmiany w Ruby 1.9 #3 - zmiany odnośnie argumentów metod

2 komentarze | Kategorie: Ruby, Techblog | trackback
Tagi:
ruby 1.9 changes approved - logo

Wpis ten jest jedną z części cyklu pt "Nowości i zmiany w Ruby 1.9". Pełną listę wpisów znajdziesz pod adresem http://radarek.jogger.pl/2008/11/30/nowosci-i-zmiany-w-ruby-1-9/.

Kontynuując mały cykl tym razem chciałbym opisać zmianę dotyczącą parametrów metod. Do tej pory deklaracja metody wyglądała następująco: def fun(r1, r2, .., rm, o1=v1, o2=v2, ..., on=vn[, *rest][, &block]).

Suchy zapis jest często ciężki w odbiorze, zatem opisując słownie mamy: lista parametrów wymaganych (może być pusta), lista parametrów opcjonalnych (może być pusta), opcjonalny argument zbierający pozostałe parametry, opcjonalny parametr bloku. Prezentuje to przykładowy kod:

def foo(a, b = 1, *rest, &block)
  p [a, b, rest, block]
end

foo(10)
foo(10, 20)
foo(10, 20, 30)

Uruchamiając powyższy program otrzymamy:

[10, 1, [], nil]
[10, 20, [], nil]
[10, 20, [30], nil]

Parametry wymagane z prawej strony listy?

Nowością, jakby nie patrzeć dosyć kontrowersyjną, jest możliwość umieszczenia wymaganych parametrów po parametrach opcjonalnych oraz *rest, ale przed &block.

Zatem formalnie zapisując wygląda to tak: def fun([r1, r2, .., rm][, o1=v1, o2=v2, ..., on=vn][, *rest][, rm+1, rm+2, ..., rm+l][, &block]).

Opisując słownie mamy: lista parametrów wymaganych (może być pusta), lista parametrów opcjonalnych (może być pusta), opcjonalny argument zbierający pozostałe parametry, lista parametrów wymaganych (może być pusta), opcjonalny parametr bloku. Prezentuje to przykładowy kod:

def foo(a, b = 1, *rest, c, &block)
  p [a, b, rest, c, block]
end

foo(10, 20)
foo(10, 20, 30)
foo(10, 20, 30, 40)

Powyższy przykład tworzy na wyjściu:

[10, 1, [], 20, nil]
[10, 20, [], 30, nil]
[10, 20, [30], 40, nil]

Na początku wydaje się bardzo ciężkie do rozszyfrowania, który parametr pójdzie do którego argumentu, ale po kilku chwilach okazuje się, że nie jest to takie trudne. Należy zwrócić uwagę na kilka rzeczy:
1) musimy przekazać przynajmniej tyle parametrów ile jest wymaganych (czyli m+l)
2) jeśli nie użyto argumentu przechwytującego resztę parametrów (*rest) to można przekazać
maksymalnie n+m+l parametrów
3) nie można przeplatać argumentów opcjonalnych z wymaganymi
4) argumenty wymagane r1..rm są dopasowywane od lewej strony parametrów wywołania
5) argumenty wymagane rm+1..rm+l są dopasowywane od prawej strony parametrów wywołania
6) pozostałe argumenty są dopasowywane do argumentów opcjonalnych i argumentu przechwytującego (*rest)

Zwróć uwagę, że punkty 1, 2, 3, 4, 6 obowiązywały do tej pory. Zatem tak na prawdę doszła tylko jedna zasada.

WTF? OMFG! LOL!

Domyślam się co chcesz powiedzieć. Zapewne coś w stylu "WTF, o co tu chodzi, po co, dlaczego? Nie widzę w tym w ogóle sensu.". Ja też podobnie na to zareagowałem (do tej pory nie spotkałem się z czymś takim). Zamiast jednak rozpaczać możemy podejść do tego na dwa sposoby (my jako społeczność). Po pierwsze można to zupełnie zignorować, udawać, że tego nie było i nie ma, nie używać i namawiać innych żeby tego nie robili. Możemy też siąść i spróbować opracować rzeczywiste zastosowania. W końcu nie raz z dziwnych pomysłów powstawały ciekawe rzeczy. A przecież Ruby posiada wiele różnych, w pewnym sensie "egzotycznych", konstrukcji, które czekają na specjalną okazję by je zastosować. W końcu ile razy użyłeś w kodzie Object#instance_eval, Module#const_missing, Module#nesting, Proc#binding? Prawdopodobnie niewiele, a może nawet w ogóle. Dlatego nie rozpaczajmy, tylko pomyślmy do czego możemy wykorzystać tą nową konstrukcję.

Koichi SASADA pokazał ostatnio na ruby-core jedno z zastosowań, które dotyczy metody []=.

class MyArray < Array
  def []=(*args, value)
    p [args, value]
  end
end

arr = MyArray.new
arr[1, 2, 3] = "foo"
arr[100] = "bar"

Program wypisuje:

[[1, 2, 3], "foo"]
[[100], "bar"]

W przypadku metod <nazwa>= jako ostatni parametr (który i tak jest wymagany) dostajemy wartość po prawej stroni przypisania. Jak widzisz możliwość umieszczenia wymaganego parametru po prawej stronie już była wcześniej w języku ;-).

Myślę, że jednym z zastosowań może być użycie pseudo "nazwanych argumentów", czyli hasha z opcjami.

# stary sposób
def before_filter(*actions)
  options = actions.last.is_a?(Hash) ? actions.pop : {}
  # ...
end

before_filter :authenticate, :authorize, :except => "list"
before_filter :authenticate, :authorize

# nowy sposób
def before_filter(*actions, options)
  # ...
end

before_filter :authenticate, :authorize, except: "list"
before_filter :authenticate, :authorize, {}

Niestety nowy sposób też ma swoją wadę, bo trzeba podać jawnie pusty hash w przypadku braku dodatkowych opcji (chociaż dla niektórych to może być zaleta).

Z parametrami metod wiąże się jeszcze jedna zmiana. Otóż argumenty można grupować za pomocą (), co spowoduje użycie semantyki przypisania równoległego do podanych parametrów. Pokazuje to ostatni już przykład.

def foo(a, (b, c))
  p [a, b, c]
end

foo(1, 2)         # a = 1, b = 2, c = nil
foo(1, [])        # a = 1, b = nil, c = nil
foo(1, [2, 3])    # a = 1, b = 2, c = 3
foo(1, [2, 3, 4]) # a = 1, b = 2, c = 3

def bar((head1, *rest1), (head2, *rest2))
  p [head1, rest1, head2, rest2]
end

bar([], [])                 # head1 = nil, rest1 = [], head2 = nil, rest2 = []
bar([1], 2)                 # head1 = 1, rest1 = [], head2 = 2, rest2 = []
bar([1, 2, 3], [4, 5, 6])   # head1 = 1, rest1 = [2, 3], head2 = 4, rest2 = [5, 6]

Jeśli spodobał Ci się wpis to może umieścisz ten blog w swoim czytniku RSS?

Komentarze

1. avatar icon Peter napisał(a) 18 Lut 2009 o godz. 02:11:

Składnia before_filter :authenticate, :authorize, except: „list” możliwa jest też bez żadnego parametru wymaganego na końcu. I na dodatek nie trzeba podawać {} jak nic nie ma

Mętna i nieintuicyjna jest ta nowa składnia z wymaganymi parametrami za opcjonalnymi. Glupi pomysl. Przyklad Koichi jest na sile, nie widac zadnej wymiernej korzysci z takiej skladni.

2. avatar icon Radarek napisał(a) 18 Lut 2009 o godz. 14:15:

Tak, możliwe że ten przykład trochę naciągany. Myślę, że to po prostu nie będzie używane w praktyce przez programistów (chyba, że przez szaleńców).

Dodaj coś od siebie

Możesz korzystać ze składni Textile.

Pola oznaczone * są wymagane.

Proszę o dodawanie komentarzy związanych z tematem postu, sprawy osobiste proszę załatwiać przez maila bądź gg.

Zastrzegam sobie prawo do moderacji komentarzy (edycja, usuwanie).