inf:ruby:programming
Table of Contents
4.
4.3. Bloki i iteratory
Bloki
- Blok to kawałek kodu pomiędzy klamrami lub
doiend, - blok to coś jak ciało anonimowej metody: przyjmuje parametry (wszystko pomiędzy
|) i nie jest wykonywany, od razu, - bloki mogą pojawić się tylko po wywołaniu jakiejś metody,
- zmienne zdefiniowane w bloku są dla niego lokalne,
- parametry do niego przekazywane również są lokalne oraz przesłaniają zmienne zdefiniowane poza blokiem,
- można zdefiniować lokalne zmienne bloku, podając je po średniku w liście parametrów. Dzięki temu, zmienne o tej samej nazwie poza blokiem nie zostaną nadpisane:
best_movie = "Young and Beautiful" ["The Great Beauty", "The Wolf of Wall Street", "Il Divo"].each do |movie; best_movie| best_movie = movie if movie.include?('Divo') end puts best_movie # => Young and Beautiful
Iteratory
- Iterator to metoda, która wywołuje blok,
- w tej metodzie, blok wywoływany jest za pomocą polecenia
yield, - gdy blok zostanie wykonany, Ruby kontynuuje wykonywanie kodu po uruchomionym
yield, - do takiego bloku można przekazywać parametry i otrzymywać od nich wartości:
def praise_her(actress) praise = yield actress puts "#{praise} very much!" end praise_her('Marine Vacth') { |a| "I love #{a}" }
- jeśli wywołamy
injectbez podania parametru, metoda użyje pierwszego elementu kolekcji jako wartości początkowej i rozpoczyna iterację z drugim elementem:> [1, 2, 3].inject() {|sum, element| sum + element} => 6 > [1, 2, 3].inject(10) {|sum, element| sum + element} => 16
Enumeratory
- Enumerator to coś w stylu zewnętrznego iteratora - podczas gdy iteratory są wewnętrzne dla danej kolekcji (jest to tylko metoda, która wywołuje
yieldgdy wygeneruje nową wartość), enumeratory to pełnoprawne obiekty, które można przekazywać metodom, - enumeratory tworzymy za pomocą metody
to_enum(lubenum_for), - metoda
loopwykonuje przekazany jej blok w nieskończoność. Kiedy jednak wykorzystamy tam obiekt enumeratora, gdy jego elementy się skończą,loopzakończy swoje działanie:> short_enum = [1, 2, 3].to_enum => #<Enumerator: [1, 2, 3]:each> > long_enum = ('a' .. 'z').to_enum => #<Enumerator: "a".."z":each> > loop {puts "#{short_enum.next} - #{long_enum.next}"} 1 - a 2 - b 3 - c => nil
- można utworzyć enumerator, przekazując mu blok. Kod z bloku będzie wykorzystany, gdy enumerator będzie musiał dostarczyć nową wartość programowi. Kod bloku nie jest po prostu wykonywany od góry na dół a równolegle z resztą programu. Wykonywanie zaczyna się na początku i zatrzymuje się, gdy blok zwraca wartość do programu. Gdy potrzebna jest kolejna wartość, kod wznawia się po poprzednim
yield. Dzięki temu, można tworzyć nieskończone enumeratory:fib = Enumerator.new do |y| a = b = 1 loop do y.yield a a, b = b, a + b end end p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
- w powyższym przykładzie, iteracja jest zdefiniowana przez podany blok, w którym obiekt
yielder, podany jako parametru bloku, może być wykorzystany by otrzymać wartość, wywołując metodęyield, - można przekonwertować blok w obiekt, przypisać go do zmiennej, przekazywać dalej i wywołać kod później,
- jeśli ostatni parametr metody poprzedzimy znakiem
&, Ruby będzie szukać bloku z każdym wywołaniem metody. Blok jest zamieniany na obiekt klasyProci przypisany parametrowi, - dobrym powodem aby przekazać blok metodzie, jest fakt, że kod w takim bloku jest obliczany ponownie.
5. Dziedziczenie, moduły i mixiny
- Dziedziczenie pozwala tworzyć klasę, która jest szczególną wersją innej klasy,
- wszystkie metody instancji są dziedziczone przez podklasę,
- często wykorzystuje się dziedziczenie aby dodać specyficzne dla danej aplikacji zachowanie do istniejącej już biblioteki lub frameworka,
- klasa w Rubym ma tylko jedną bezpośrednią nadklasę, może jednak zawierać w sobie wiele mixinów (tj. częściowych definicji klas),
- moduły to sposób na grupowanie razem metod, klas i stałych,
- moduły zapewniają przestrzeń nazw, zapobiegając kolizjom,
- moduł nie może mieć instancji, ponieważ nie jest klasą. Można jednak dołączać moduły do klas. Wszystkie metody instancji będą dostępne w tej klasie - zostają w nią wmieszane (
mixin), - gdy moduł jest dołączany do klasy, staje się jej nadklasą,
- polecenie
includetworzy referencję do modułu. Jeśli ten moduł jest w innym pliku, musi zostać wcześniej załadowany (poprzezrequirelubload). Jeśli moduł jest zawarty w wielu klasach i jest zmieniony podczas działania programu, wszystkie klasy będą miały uaktualnione metody z tego modułu, - prawdziwa siła mixinów pojawia się, gdy metody z modułu współdziałają z metodami obiektu,
- moduł dołączany do klasy może w niej tworzyć zmienne instancji i może używać
attr_readeri jemu podobnych aby zdefiniować akcesory dla tych zmiennych, - może to doprowadzić do kolizji ze zmiennymi klasy bądź innych mixinów,
- w większości wypadków, moduły dołączane do klas nie korzystają ze zmiennych instancji klasy bezpośrednio, ale za pomocą akcesorów, jeśli jednak jest konieczne aby mixin posiadał swój własny stan, trzeba się upewnić, że zmienne mają unikalne nazwy,
- trzeba jednak pamiętać, że jeśli mixin wymaga takich zabiegów, to chyba powinien być pełnoprawną klasą,
- szukając metody, Ruby najpierw sprawdza w bezpośredniej klasie obiektu, następnie w mixinach dołączonych do tej klasy, a w końcu w nadklasie i jej mixinach. Jeśli klasa ma wiele mixinów, dołączony ostatnio jest najpierw przeszukiwany,
- dziedziczenie to bardzo silne połączenie dwóch komponentów - każda zmiana nadklasy to duże ryzyko problemów w podklasie. Jest to poważne ograniczenie zwinności programu i dlatego odchodzi się od dziedziczenia (is a) w stronę kompozycji (has a, uses a).
6. Typy danych
Liczby
- Liczby całkowite mogą być o dowolnej długości, do wyczerpania pamięci komputera,
- w pewnych przedziałach, przechowywane są jako obiekty klasy
Fixnum(-2^30…2^30 - 1 lub -2^62…2^62-1) - poza tym przedziałem, liczby przechowywane są jako
Bignum. Proces przechodzenia pomiędzy przedziałami jest transparentny - zapisuje się je wykorzystując:
- opcjonalny znak (
-), - opcjonalny wskaźnik systemu pozycyjnego (
0- ósemkowy,0d- dziesiętny,0x- szesnastkowy,0b- binarny), - lista cyfr,
- liczba z częścią po przecinku jest obiektem
Float, - Ruby obsługuje liczby rzeczywiste i złożone:
Rational(3, 4) * Rational(2, 3) # => (1/2) Complex("1+2i") * Complex("3+4i") # => (-5+10i)
- jeśli na dwóch liczbach różnych klas wykonywana jest jakaś operacja, wynikiem będzie obiekt bardziej ogólnej klasy (Fixnum dodany do Floata zwróci Float, Float dodany do liczby złożonej, zwróci liczbę złożoną).
Łańcuchy znaków
- W łańcuchach zawartych pomiędzy pojedynczym cudzysłowem, dwa kolejne backslashe są zastępowane jednym, a pojedynczy cudzysłów po backslashu staje się pojedynczym cudzysłowem,
24. Metaprogramowanie
- Metaprogramowanie to pisanie kodu, który generuje kod,
- metaprogramując, nie jesteśmy ograniczeni do abstrakcji wbudowanych w dany język programowania; tworzymy własny poziom abstrakcji, bliższy domenie problemu, który staramy się rozwiązać,
- metaprogramowanie umożliwia uproszczenie kodu,
24.1. Obiekty i klasy
- W Rubym istnieje koncepcja obecnego obiektu. Obecny obiekt jest zreferencjonowany przez wbudowaną, niemodyfikowalną zmienną
self, - gdy odczytujemy zmienne instancji, Ruby szuka ich w obiekcie zreferencjonowanym przez
self, - każda metoda jest wywoływana na jakimś obiekcie (na przykład
movie.lengthwywołuje metodęlengthna obiekciemovie, który nazywamy odbiorcą wywołania). Jeśli wywołujemy metodę bez podania odbiorcy (puts string), Ruby jako odbiorcę ustawia obecny obiekt, zreferencjonowany przezselfi tam szuka metody. W obu przypadkach, proces ten jest zbliżony, ponieważ podanie odbiorcy spowoduje ustawienieselfna obiekt odbiorcy (w naszym przypadku obiekt zreferencjonowany przezmovie) na czas wywołania metody, selfjest zmieniana również podczas definiowania klasy. Jest to spowodowane tym, że w Rubym klasy to również wykonywalny kod, a jeżeli jesteśmy w stanie wykonać jakiś kod, musi on mieć obiekt.class Vcard puts "self = #{self}, self.class = #{self.class}" end # => self = Vcard, self.class = Class
selfjest ustawiona na klasęVcard, która również jest obiektem. Zmienne instancji, zdefiniowane w środku definicji klasy będą dostępne metodom klasy, ponieważselfbędzie takie samo gdy były one definiowane i kiedy metody były uruchamiane.class Ticket @price = 100 def self.first_price @price end end Ticket.first_price # => 100
24.2 Singletony
- Ruby umożliwia definiowanie metod konkretnemu obiektowi.
class Ticket; end ticket_1 = Ticket.new ticket_2 = Ticket.new def ticket_1.price puts "Too much!" end ticket_1.price ticket_2.price # => Too much! # => singleton_methods.rb:11:in `<main>': undefined method `price' for #<Ticket:0x91292ac> (NoMethodError)
- gdy zdefiniowaliśmy metodę singletonową dla
ticket_1, Ruby utworzył nową anonimową klasę i zdefiniował w niej metodęprice. Ta anonimowa klasa, nazywana klasą singletonową lub eigenclass jest ustawiana jako superklasa klasyTicket, ale tylko dla obiektuticket_1, - w praktyce, coś takiego jak metody klasy nie istnieją. Zamiast tego, definiujemy metody singletonowe na obiektach klasy
Classi wywołujemy je na tym obiekcie, - drugi sposób na definiowanie metody singletonowej wygląda następująco:
class << ticket_1 def info puts "I am a #{self}" end end
- w środku powyższej definicji,
selfjest ustawiane do klasy singletonowej. Ponieważ definicje klas zwracają wartość ostatnio wykonanego polecenia, możemy to wykorzystać do dostania się do obiektu singletona:class Ticket; end ticket_1 = Ticket.new def ticket_1.price puts "Too much!" end singleton = class << ticket_1 def info puts "Ticket from Knurow to Zernica" end self end puts "Singleton: #{singleton}" puts "Singleton methods: #{singleton.instance_methods - Ticket.new.methods}" # Singleton: #<Class:#<Ticket:0x8bcded4>> # Singleton methods: [:price, :info]
- Ruby uniemożliwia wykorzystanie takiego obiektu poza kontekstem ich pierwotnego obiektu. Nie można utworzyć nowej instancji obiektu singletonowego.
24.3. Dziedziczność i widoczność
- W definicji klasy, można zmienić widoczność metody dziedziczonej z superklasy. Ruby wstawia niewidoczną metodę w klasie, która wywołuje tą z superklasy za pomocą metody
superi ustawia jej widoczność na tą, którą wybraliśmy;supermoże wywoływać metody superklasy bez względu na ich widoczność.
24.4. Moduły i Mixiny
include
includejest tak zaimplementowane, że wstawiany moduł jest ustawiany jako klasa nadrzędna klasy, do której moduł wstawiamy,- z tego powodu, moduł musi przechowywać informacje poprzedniej superklasie obiektu, do którego jest wstawiany. Ponadto, moduł może być wstawiany do różnych obiektów o różnych superklasach - jeden obiekt modułu nie wystarczy. Aby sobie z tym poradzić, Ruby tworzy nowy obiekt klasy, ustawia go jako superklasę klasy, do której moduł jest wstawiany. Superklasą nowo utworzonego obiektu zostaje superklasa klasy, do której moduł jest wstawiany. Na końcu referencjonuje metody modułu w nowej klasie, że wywołując metodę na nowym obiekcie, jest ona wyszukiwana w module (
)
- zmiany w module uwidaczniają się w klasie nawet po jego wstawieniu,
- wstawiając moduł, do którego wstawione są inne moduły, uzyskamy dostęp do metod z wszystkich modułów,
- Ruby wstawi moduł tylko raz w łańcuchu dziedziczenia.
extend
- jeśli chcemy dodać moduł tylko do konkretnego obiektu, korzystamy z metody
Object#extend, - jeśli wykorzystamy metodę w definicji klasy, metody modułu stają się metodami klasy. Dzieje się tak, ponieważ wywołanie
extendto ekwiwalentself.extend, metody dodawane są zatem doself, co w definicji klasy jest tą klasą.
24.5. Metaprogramowanie - makra z poziomu klasy
- Hierarchia klas singletonowych jest równoległa do hierarchii normalnej klasy. Wynikiem tego jest to, że metody klasy w klasie nadrzędnej są również dostępne w klasie podrzędnej:
class Logger def self.add_logging def log(message) STDERR.puts Time.now.strftime("%H:%M:%S:") + "#{self} (#{message})" end end end class Example < Logger add_logging end ex = Example.new ex.log("Haii")
- metoda
define_method, przyjmując nazwę metody i blok, definiuje metodę o podanej nazwie i ciele metody z bloku. Każdy argument w definicji bloku staje się parametrem nowej metody.class Logger def self.add_logging(id_string) define_method(:log) do |msg| now = Time.now.strftime("%H:%M:%S") STDERR.puts "#{now}-#{id_string}: #{self} (#{msg})" end end end class Song < Logger add_logging "Song" end class Album < Logger add_logging "Album" end Song.new.log("tralala")
- sprawy nieco się komplikują, gdy chcemy dodać zarówno metody klasy jak i obiektu do nowo definiowanej klasy. Jedną z technik wykorzystywanych w Railsach jest metoda
included, która jest uruchamiana za każdym razem, gdy dołącza się moduł do klasy.module GeneralLogger def log(msg) puts Time.now.strftime("%H:%M: ") + msg end module ClassMethods def attr_logger(name) attr_reader name define_method("#{name}=") do |val| log "Assigning #{val.inspect} to #{name}" instance_variable_set("@#{name}", val) end end end def self.included(receiver) receiver.extend ClassMethods end end class Example include GeneralLogger attr_logger :value end ex = Example.new ex.log("Hello!") ex.value = 123 puts "Value is #{ex.value}" ex.value = "Snooker" puts "Value is #{ex.value}"
25. Refleksja, ObjectSpace i rozproszony Ruby
25.3. Dynamiczne wywoływanie metod
- Metoda
Object#sendumożliwia wywołanie metody na obiekcie.[1, 2, 3, 4].send(:size) # => 4 "Olivia Wilde".send("sub", "livia", "scar") # => "Oscar Wilde"
- Obiekt klasy
Methodumożliwia to samo. Zachowuje się jakProc, to znaczy że zachowuje kontekst, w którym uruchamia.olivia = "Olivia Wilde".method("sub") # => #<Method: String#sub> olivia.call("livia", "scar") # => "Oscar Wilde" = "Olivia Wilde".method("sub")
inf/ruby/programming.txt · Last modified: 2021/02/16 09:56 by 127.0.0.1
