Czy zastanawialiście się kiedyś nad metodami to_a
i to_ary
w Ruby? Czy to w zasadzie dwie nazwy na tą samą metodę? A może jest między nimi jakaś różnica? Co jeżeli zaimplementujemy te metody w naszej własnej klasie? W tym artykule postaram się odpowiedzieć na te pytania. Zaczynamy!
Na początek zacznijmy od definicji metod to_a
i to_ary
w klasie Array
, jakie możemy znaleźć w dokumentacji Ruby Doc:
to_a
- Kiedyself
jest instancją klasyArray
, zwróćself
. W przeciwnym wypadku, zwróć nowy obiekt klasyArray
zawierający elementy zself
.
to_ary
- Zwróćself
.
Już na tym etapie widzimy, że definicje te są podobne, ale nie dokładnie takie same. W przypadku implementacji tych metod w naszych własnych klasach możemy powiedzieć, że metoda to_ary
jest używana do konwersji implicit, natomiast to_a
do konwersji explict. To może brzmieć niezrozumiale, dlatego napiszę to innymi słowami. Metoda to_ary
pozwala obiektowi zachowywać się jak obiekt klasy Array
, a metoda to_a
zamienia, czyli konwertuje nasz obiekt na obiekt klasy Array
. Ten wzorzec postępowania jest wykorzystywany wielokrotnie w języku Ruby. Między innymi dla par metod takich jak to_s
i to_str
oraz to_i
i to_int
.
Teraz możemy już przejść do przykładów. Stwórzmy klasę Point
, która będzie implementować metody to_a
i to_ary
tak byśmy mogli zobaczyć ich użycie.
class Point
def initialize(x, y)
@x = x
@y = y
end
def to_a
puts 'to_a method called'
[x, y]
end
def to_ary
puts 'to_ary method called'
[x, y]
end
def inspect
"#<#{self.class.name} (#{x}, #{y})>"
end
private
attr_reader :x, :y
end
Sprawdźmy co stanie się, gdy użyjemy splat operatora *
na obiekcie klasy Point
.
point = Point.new(1, 3)
# => #<Point (1, 3)>
Point.new(*point)
# to_a method called
# => #<Point (1, 3)>
Jak widzimy została wywołana metoda to_a
. Z drugiej strony kiedy spróbujemy przypisać obiekt klasy Point
do zmiennych zostanie użyta metoda to_ary
.
x, y = point
# to_ary method called
# => #<Point (1, 3)>
Jako następny krok sprawdźmy, co się dzieje gdy użyjemy each
na kolekcji punków.
[point].each { |item| item }
# => [#<Point (1, 3)>]
[point].each { |(x, y)| [x, y] }
# to_ary method called
# => [#<Point (1, 3)>]
W pierwszym przykładzie nie dzieje się nic szczególnego. Po prostu iterujemy przez kolekcję punktów. Natomiast w drugim przykładzie widzimy, że została wywołana metoda to_ary
. Stało się tak dlatego, że podobnie jak powyżej i tym razem przypisaliśmy obiekt klasy Point
do dwóch zmiennych. Warto tu wspomnieć, że w implementacji klasy Point
koordynaty punktu są prywatne, a dzięki metodzie to_ary
możemy się do nich w łatwy sposób dostać podczas używania metody each
.
Jakiś czas temu napisałam artykuł o tym jak Ruby rzutuje obiekty na łańcuchy znaków, w którym opisywałam jak zachowuje się metoda puts
dla obiektów typu Array
. Zobaczmy co się stanie w przypadku klasy Point
, która ma się zachowywać podobnie do Array
.
puts point
# to_ary method called
# 1
# 3
# => nil
Kiedy metoda to_ary
jest zaimplementowana w klasie Point
, metoda puts
postara się użyć to_ary
by podzielić nasz obiekt na mniejsze części i wywołać puts
rekursywnie. Czyli na każdym pojedynczym elemencie.
Teraz jest dobry moment by wspomnieć o ważnej rzeczy. Gdy nasz obiekt typu Point
nie implementuje metody to_ary
przypisanie do zmiennej oraz inne zachowania będą wyglądać inaczej, ale nie dostaniemy wyjątku. Oto przykład:
class Point
def initialize(x, y)
@x = x
@y = y
end
def to_a
puts 'to_a method called'
[x, y]
end
def inspect
"#<#{self.class.name} (#{x}, #{y})>"
end
private
attr_reader :x, :y
end
point = Point.new(1, 3)
# => #<Point (1, 3)>
x, y = point
# => #<Point (1, 3)>
x
# => #<Point (1, 3)>
y
# => nil
Jak widać obiekt klasy Point
nie został w tym momencie podzielony na składowe. Zerknijmy jeszcze na zachowanie względem metody puts
bez implementacji metody to_ary
.
point = Point.new(1, 3)
# => #<Point (1, 3)>
puts point
# #<Point:0x00007f6e92b2a8b0>
# => nil
W tym przypadku również metoda puts
nie iteruje przez składowe obiektu klasy Point
. Podobne obserwacje możemy zauważyć też dla metody flatten
gdy metoda to_ary
nie jest definiowana.
point_1 = Point.new(1, 3)
# => #<Point (1, 3)>
point_2 = Point.new(1, 5)
# => #<Point (1, 5)>
[point_1, point_2].flatten
# => [#<Point (1, 3)>, #<Point (1, 5)>]
Z drugiej strony, gdy użyjemy jej na obiekcie klasy Point
z zaimplementowaną metodą to_ary
wynik będzie inny.
point_1 = Point.new(1, 3)
# => #<Point (1, 3)>
point_2 = Point.new(1, 5)
# => #<Point (1, 5)>
[point_1, point_2].flatten
# to_ary method called
# to_ary method called
# => [1, 3, 1, 5]
Metoda flatten
wywoła metodę to_ary
na każdym elemencie tablicy. Zachęcam do samodzielnego sprawdzenia co się stanie w przypadku metody join
.
Podsumowanie
- Dwie metody pozwalają nam na zachowywanie się jak
Array
:- metoda
to_a
- pozwala na rzutowanie obiektu na obiekt typuArray
. - metoda
to_ary
- pozwala nam zachowywać się jak obiekt typuArray
.
- metoda
- Splat operator używa metody
to_a
. - Przypisanie obiektu do zmiennych będzie chciało użyć metody
to_ary
jeżeli taka istnieje. Jeżeli nie, przypisanie nastąpi tylko dla pierwszej zmiennej. Reszta pozostanie z wartościąnil
. puts
będzie starać się użyć metodyto_ary
jeżeli jest ona zaimplementowana w obiekcie- Metody
flatten
ijoin
będą się zachowywać podobnie jakputs
. Jeżeli w obiekcie będzie zaimplementowana metodato_ary
to z niej skorzystają.
Linki
- Jak Ruby rzutuje obiekty na łańcuchy znaków?
- Array Ruby Doc - EN
- What’s the difference between to_a and to_ary? - Stack Overflow - EN
Potrzebujesz pomocy?
Jeśli szukasz doświadczonej programistki Ruby z ponad dziesięcioletnim stażem, śmiało skontaktuj się ze mną.
Mam doświadczenie w różnych domenach, a szczególną wagę przykładam do szybkiej reakcji na opinie użytkowników i pracy zespołowej. Pomogę Ci stworzyć świetny produkt.