====== 4. ======
===== 4.3. Bloki i iteratory =====
==== Bloki ====
* Blok to kawałek kodu pomiędzy klamrami lub ''do'' i ''end'',
* 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 ''inject'' bez 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 ''yield'' gdy wygeneruje nową wartość), enumeratory to pełnoprawne obiekty, które można przekazywać metodom,
* enumeratory tworzymy za pomocą metody ''to_enum'' (lub ''enum_for''),
* metoda ''loop'' wykonuje przekazany jej blok w nieskończoność. Kiedy jednak wykorzystamy tam obiekt enumeratora, gdy jego elementy się skończą, ''loop'' zakończy swoje działanie:
> short_enum = [1, 2, 3].to_enum
=> #
> long_enum = ('a' .. 'z').to_enum
=> #
> 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 klasy ''Proc'' i 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 ''include'' tworzy referencję do modułu. Jeśli ten moduł jest w innym pliku, musi zostać wcześniej załadowany (poprzez ''require'' lub ''load''). 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_reader'' i 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.length'' wywołuje metodę ''length'' na obiekcie ''movie'', który nazywamy odbiorcą wywołania). Jeśli wywołujemy metodę bez podania odbiorcy (''puts string''), Ruby jako odbiorcę ustawia obecny obiekt, zreferencjonowany przez ''self'' i tam szuka metody. W obu przypadkach, proces ten jest zbliżony, ponieważ podanie odbiorcy spowoduje ustawienie ''self'' na obiekt odbiorcy (w naszym przypadku obiekt zreferencjonowany przez ''movie'') na czas wywołania metody,
* ''self'' jest 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
''self'' jest ustawiona na klasę ''Vcard'', która również jest obiektem. Zmienne instancji, zdefiniowane w środku definicji klasy będą dostępne metodom klasy, ponieważ ''self'' bę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 `': undefined method `price' for # (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 klasy ''Ticket'', ale tylko dla obiektu ''ticket_1'',
* w praktyce, coś takiego jak metody klasy nie istnieją. Zamiast tego, definiujemy metody singletonowe na obiektach klasy ''Class'' i 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, ''self'' jest 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: #>
# 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 ''super'' i ustawia jej widoczność na tą, którą wybraliśmy; ''super'' może wywoływać metody superklasy bez względu na ich widoczność.
===== 24.4. Moduły i Mixiny =====
==== include ====
* ''include'' jest 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 (FIXME)
* 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 ''extend'' to ekwiwalent ''self.extend'', metody dodawane są zatem do ''self'', 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#send'' umożliwia wywołanie metody na obiekcie.
[1, 2, 3, 4].send(:size)
# => 4
"Olivia Wilde".send("sub", "livia", "scar")
# => "Oscar Wilde"
* Obiekt klasy ''Method'' umożliwia to samo. Zachowuje się jak ''Proc'', to znaczy że zachowuje kontekst, w którym uruchamia.
olivia = "Olivia Wilde".method("sub")
# => #
olivia.call("livia", "scar")
# => "Oscar Wilde" = "Olivia Wilde".method("sub")