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- Kiedyselfjest instancją klasyArray, zwróćself. W przeciwnym wypadku, zwróć nowy obiekt klasyArrayzawierają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_aryjeżeli taka istnieje. Jeżeli nie, przypisanie nastąpi tylko dla pierwszej zmiennej. Reszta pozostanie z wartościąnil. putsbędzie starać się użyć metodyto_aryjeżeli jest ona zaimplementowana w obiekcie- Metody
flattenijoinbędą się zachowywać podobnie jakputs. Jeżeli w obiekcie będzie zaimplementowana metodato_aryto 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.
Woman on Rails Newsletter
Dołącz do społeczności pasjonatów IT i otrzymuj krótkie, wartościowe maile na temat rozwoju osobistego, programowania, produktywności i zarządzania zespołem. A od czasu do czasu również moje osobiste spostrzeżenia i historie ze świata IT.