====== Dynamiczne wykonywanie kodu ======
Jako dynamiczny, interpretowany jezyk, Ruby umozliwia uruchamianie kodu generowanego dynamicznie. Robi sie to za pomoca metody ''eval''.
* ''eval "puts 2 + 2"'' wyswietli 4 ale zwroci ''nil'', bo to jest zawsze zwracane przez metode ''puts'',
* ''puts eval("2 + 2")'' zwroci 4.
===== Bindings =====
W Rubym binding to referencja do jakiegos kontekstu, zakresu lub stanu wykonania.
# bindings.rb
def binding_elsewhere
x = 20
return binding
end
remote_binding = binding_elsewhere
x = 10
eval("puts x")
eval("puts x", remote_binding)
''eval'' akceptuje drugi parametr, wlasnie te bindingi. W powyzszym przypadku, ten binding zwracany jest z metody ''binding_elsewhere''. Zmienna ''remote_binding'' zawiera referencje do kontekstu wykonania wewnatrz ''binding_elsewhere'', a nie w glownym kodzie. Dlatego wyswietlajac x, 20 jest pokazywane, poniewaz tyle wlasnie wynosi ono wewnatrz ''binding_elsewhere''.
===== Inne formy eval'a =====
==== class_eval ====
''class_eval'' idealny jest do dynamicznego dodawania metod do klasy:
# class_eval.rb
class Person
end
def add_accessor_to_person(accessor_name)
Person.class_eval %Q{
attr_accessor :#{accessor_name}
}
end
person = Person.new
add_accessor_to_person :name
add_accessor_to_person :gender
person.name = "Peter Cooper"
person.gender = "male"
puts "#{person.name} is #{person.gender}"
Korzystamy z metody ''add_accessor_to_person'' aby dynamicznie dodac akcesory do klasy ''Person''. Innym rozwiazaniem jest rozszerzenie klasy ''Class'' o metode dodajaca akcesory:
# class_eval_2.rb
class Class
def add_accessor(accessor_name)
self.class_eval %Q{
attr_accessor :#{accessor_name}
}
end
end
class Person
end
person = Person.new
Person.add_accessor :name
Person.add_accessor :gender
person.name = "Petr Cooper"
person.gender = "male"
puts "#{person.name} is #{person.gender}"
==== instance_eval ====
''instance_eval'' to cos jak zwykly ''eval'' ale w kontekscie obiektu (a nie metody):
# instance_eval.rb
class MyClass
def initialize
@my_variable = 'Hello, world!'
end
end
obj = MyClass.new
obj.instance_eval { puts @my_variable }
===== Tworzenie wlasnej wersji attr_accessor'a =====
# own_attr_acc.rb
class Class
def add_accessor(accessor_name)
self.class_eval %Q{
def #{accessor_name}
@#{accessor_name}
end
def #{accessor_name}=(value)
@#{accessor_name} = value
end
}
end
end
Powyzsze to odpowiednik standardowego akcesora:
def name
@name
end
def name=(value)
@name = value
end
====== Uruchamianie innych programow z Rubego ======
===== Otrzymywanie wynikow z innych programow =====
Mamy trzy rozne metody uruchamiania zewnetrznych programow z poziomu Rubego (podobnie jak w Perlu):
* metoda ''system'' modulu ''Kernel'' - idealna, gdy nie interesuje nas to co wyswietli program,
* za pomoca tzw. //backtickow// - ''``'' - gdy potrzebujemy tego co program wyswietla,
* za pomoca //delimited input literals// - ''%x{}'' - funkcjonalnie to odpowiednik //backtickow//.
===== Przekazywanie wykonywania do innego programu =====
Aby zakonczyc wykonywanie obecnego programu i uruchomic inny, skorzystac mozna z metody ''exec''.
exec "ruby another_script.rb"
puts "To nigdy nie zostanie wyswietlone."
===== Uruchamianie dwoch programow jednoczesnie =====
//Forking// ma miejsce, gdy proces duplikuje sie, w wyniku czego powstaja dwa procesy programu dzialajace jednoczesnie. W Rubym mamy metode ''fork'' modulu ''Kernel'', ktora tworzy taki fork. Metoda zwraca PID utworzonego procesu w glownym procesie a ''nil'' w procesie utworzonym (//child//). Forkowanie nie jest mozliwe w wersji Rubego na Windowsa.
# forking.rb
if fork.nil?
exec "ruby some_other_file.rb"
end
puts "This script now continues to run alongside some_other_file.rb"
Jesli uruchamiany przez ''exec'' program ma sie kiedys zakonczyc i chcemy na to poczekac, musimy skorzystac z ''Process.wait''.
# waiting.rb
child = fork do
sleep 3
puts "Child says 'Haizz'!"
end
puts "Waiting for the child process..."
Process.wait child
puts "All done!"
===== Interakcja z innym programem =====
Dzieki modulowi ''IO'' Rubego i jego metodzie ''popen'' mamy mozliwosc utworzenia strumienia I/O pomiedzy programami. Dziala to tylko w wypadku, gdy programy akceptuja bezposrednie wejscie i produkuja bezposrednie wyjscie w linii polecen.
# iols.rb
ls = IO.popen("ls", "r")
while line = ls.gets
puts line
end
ls.close
Powyzej, otwieramy strumien pomiedzy naszym programem a linuksowym ''ls''. Wczytujemy przez niego, linia po linii, nazwy plikow w katalogu.
====== Bezpieczne obchodzenie sie z danymi, niebezpieczne metody ======
Aplikacje czesto pracuja w srodowiskach i sytuacjach, w ktorych nie mozna zagwarantowac poprawnosci danych wejsciowych. Konieczne jest zabezpieczanie sie przeciw takim sytuacjom.
===== Skazone dane i obiekty =====
Wszystkie dane, ktore pochodza z zewnetrznych zrodel, lub takich zrodel, ktorych Ruby nie potrafi okreslic czy sa bezpieczne, uznawane sa jako skazone. Dane pochodzace z plikow, czy z linii polecen uznawane sa jako skazone. Dane zapisane w programie w postaci literalow uznawane sa jako nieskazone.
while x = gets
puts "=> #{eval(x)}"
end
Przyklad wydaje sie niegrozny, ale wystarczy wpisac powyzej trwozace krew w zylach polecenie ''rm -rf /*'' i caly swiat ulegnie zniszczeniu.
Mozna sprawdzic, czy obiekt nie jest skazony za pomoca metody ''tainted?''. Mozna zmienic obiekt ze skazonego na nieskazony za pomoca metody ''untaint''.
# untaint.rb
def code_is_safe?(code)
code =~ /[`;*-]/ ? false : true
end
while x = gets
x.untaint if code_is_safe?(x)
next if x.tained?
puts "=> #{eval(x)}"
end
Poprawiony przyklad. Dokonujemy (bardzo podstawowego) sprawdzenia za pomoca ''code_is_safe?'' i na tej podstawie odkazamy wpisane polecenie umozliwiajac jednoczesnie jego uruchomienie za pomoca ''eval''. ''next'' przeskakuje do nastepnej iteracji najbardziej wewnetrznej petli.
===== Poziomy bezpieczenstwa =====
Duzo silniejsza forma zabezpieczen sa poziomy bezpieczenstwa (//safe levels//). Poziomy te okreslaja, jakie funkcje Ruby udostepni i jak powinien radzic sobie ze skazonymi danymi. Funkcjonalnosc ta nie jest czesto uzywana, poniewaz utrudnia dzialanie bibliotek oraz poniewaz wiekszosc developerow woli pisac kod, ktory jest bezpieczny bez koniecznosci stosowania tych technik.
Aktualnie ustawiony poziom mozna sprawdzic za pomoca zmiennej ''$SAFE''. Standardowo jest to poziom 0, najnizszy.
^ ''$SAFE'' ^ Opis ^
| 0 | Brak ograniczen. |
| 1 | Potencjalnie niebezpieczne metody nie moga korzystac ze skazonych danych. Obecny katalog nie jest dodawany do sciezek, z ktorych Ruby laduje biblioteki |
| 2 | FIXME|
| 3 | FIXME|
| 4 | FIXME|
====== Watki ======
threads = []
10.times do
thread = Thread.new do
10.times { |i| print i; $stdout.flush; sleep rand(2) }
end
threads << thread
end
threads.each { |thread| thread.join }
Output:
0000000001111220345123111122312326454223423334376555344544585756966678664568797797767788899888899999
Tworzymy tablice do przechowywania watkow, aby miec latwy do nich dostep. Nastepnie tworzymy dziesiec watkow, wysylajac blok kodu do uruchomienia do metody ''Thread.new'', jednoczesnie dodajac je do tablicy. Po utworzeniu watkow, czekamy, az wszystkie ukoncza dzialanie. Robimy to za pomoca metody ''join'' kazdego watku, ktora powoduje, ze glowny program czeka az watki zakoncza dzialanie zanim wznowi swoje wlasne.
===== Zaawansowane operacje z watkami =====
Metoda ''join'' przyjmuje parametr, za pomoca ktorego mozemy okreslic //timeout//, po ktorym metoda ''join'' zwroci ''nil''.
threads.each do |thread|
puts "Thread #{thread.object_id} didn't finish in 1s" unless thread.join(1)
end
Metoda ''Thread.list'' zwraca globalna liste wszystkich watkow. Warto jednak zawsze miec swoja wlasna, jesli w programie korzysta sie zwiecej niz jednej grupy watkow i chcemy trzymac je osobno do uzycia z ''join'' lub innymi funkcjami.
# threads_02.rb
10.times { Thread.new { 10.times { |i| print i; $stdout.flush; sleep rand(2) } } }
Thread.list.each { |thread| thread.join unless thread == Thread.main }
Lista zwracana metoda ''list'' zawiera rowniez watek glowny, musimy zatem wykonac sprawdzenie.
Watki potrafia komunikowac sie z schedulerem watkow i dostarczac informacji o ich statusie. Watek moze sie sam zatrzymac, za pomoca metody ''Thread.stop''. Mozna go wznowic lub zrestartowac z programu glownego za pomoca metody ''run''.
Thread.list.each { |thread| thread.run }
Watek moze rowniez powiedziec schedulerowi, ze chce przekazac wykonywanie do innego watku. Taka technika zwana jest //cooperative multitasking//. Poprawnie wykorzystana, potrafi zwiekszyc wydajnosc wielowatkowosci.
# thread_stop.rb
2.times { Thread.new { 10.times { |i| print i; $stdout.flush; Thread.pass } } }
Thread.list.each { |thread| thread.join unless thread == Thread.main }
Output:
01234567890123456789
====== Wlokna (fibers) ======
Wlokna to alternatywa dla watkow w Rubym 1.9 i nowszych. Wlokna to lekkie jednostki wykonywania, ktore kontroluja swoj wlasny scheduler (//cooperative scheduling//). Podczas gdy watki zazwyczaj dzialaja ciagle, wlokna, po wykonaniu zadania, przekazuja kontrole schedulerowi, to znaczy istnieja dalej i moga byc wznawiane.
# fiber.rb
sg = Fiber.new do
s = 0
loop do
square = s * s
Fiber.yield square
s += 1
end
end
10.times { puts sg.resume }
Wlokno bedzie dzialac, dopoki nie wywolamy ''Fiber.yield'', aby oddac kontrole, temu kto ostatni kazal wloknu sie uruchomic (w tym przypadku metodzie ''sg.resume''). Pomimo tego, ze wlokno posiada nieskonczona petle, nie dziala ono caly czas, nie powodujac spadku wydajnosci. Jesli celowo damy wloknu jakis punkt jego zakonczenia, przywolanie jego metody ''resume'' spowoduje wyjatek.
===== Przekazywanie danych do wlokna =====
# fiber_data.rb
sg = Fiber.new do
s = 0
loop do
square = s * s
s += 1
s = Fiber.yield(square) || s
end
end
puts sg.resume
puts sg.resume
puts sg.resume
puts sg.resume
puts sg.resume 40
puts sg.resume
puts sg.resume
puts sg.resume 0
puts sg.resume
puts sg.resume
Output:
0
1
4
9
1600
1681
1764
0
1
4
W piatej probie, przekazujemy liczbe 40, ktora jest przypisywana zmiennej ''s'' wlokna. Kilka iteracji pozniej, zerujemy ''s''. Wartosc jest odbierana przez wlokno w wyniku wywolania ''Fiber.yield''.
===== Dlaczego wlokna? =====
* W niektorych sytuacjach wieksza wydajnosc. Utworzenie setek wlokien jest *duzo* szybsze niz watkow, w szczegolnosci w przypadku Rubego 1.9, gdzie watki tworzone sa na poziomie systemu operacyjnego. Wieksza jest rowniez wydajnosc pamieciowa.
* Mozemy uruchomic tylko jedno wlokno w danym czasie (w jednym watku) i my musimy zajac sie schedulingiem, co w niektorych sytuacjach moze okazac sie zaleta.
====== RubyInline ======
Jako jezyk wysokiego poziomu, zorientowany obiektowo, Ruby nie zostal zaprojektowany do sytuacji wymagajacych wysokiej wydajnosci w tradycyjnym tego slowa znaczeniu. Obecnie, obliczenia nie sa wymagajace, niemniej zdazaja sie sytuacje, gdzie wysoka wydajnosc jest niezbedna.
Wtedy dobrym pomyslem moze byc napisanie tych czesci programu w innym jezyku i uruchomic taki kod z Rubego. Istnieje biblioteka, RubyInline, ktora to umozliwia. Najczesciej wykorzystywana jest do pisania w C lub Cpp.
Idealnym przykladem ilustrujacym RubyInline i potege C moze byc utworzenie podstawowej metody (funkcji w nomenklaturze C) aby obliczac silnie.
# rubyoffline.rb
class Fixnum
def factorial
(1 .. self).inject { |a, b| a * b }
end
end
require 'benchmark'
Benchmark.bm do |bm|
bm.report('ruby:') do
100000.times do
8.factorial
end
end
end
Output:
user system total real
ruby: 0.190000 0.010000 0.200000 ( 0.200773)
inject: http://www.ruby-doc.org/core/classes/Enumerable.html#M001494
A teraz z wykorzystaniem C. Musialem wprowadzic pare zmian, bo oryginalny przyklad z ksiazki nie dzialal. Zakomentowalem utworzenie singletona z tego obiektu (aby nie musiec go tworzyc) ''class << self''. Dodalem tworzenie instancji ''cfact = CFactorialnew''. http://www.sitepoint.com/forums/ruby-227/rubyinline-errors-calling-static-inline-methods-621556.html
# rubyinline.rb
require 'rubygems'
require 'inline'
require 'benchmark'
class CFactorial
# class << self
inline do |builder|
builder.c %q{
long factorial(int value) {
long result = 1, i = 1;
for(i = 1; i <= value; i++) {
result *= i;
}
return result;
}
}
end
# end
end
cfact = CFactorial.new
Benchmark.bm do |bm|
bm.report('c:') do
100000.times { cfact.factorial(8) }
end
end
Output:
user system total real
c: 0.010000 0.000000 0.010000 ( 0.016394)
Roznica jest dramatycznie duza.
====== Unicode, kodowanie znakow, wsparcie dla UTF-8 ======
Unicode to standard przedstawienia znakow kazdego pisma na swiecie. To jedyny sposob aby zarzadzac wieloma roznymi alfabetami i zestawami znakow.
Wiecej: http://www.joelonsoftware.com/articles/Unicode.html
Od wersji 1.9, Ruby obsluguje znaczna liczbe kodowan (95). Wsparcie istnieje nie tylko dla lancuchow znakow ale rowniez i dla samego kodu zrodlowego!
ruby-1.9.2-p180 :001 > Encoding.list.join(" ")
=> "ASCII-8BIT UTF-8 US-ASCII Big5 Big5-HKSCS Big5-UAO CP949 Emacs-Mule EUC-JP EUC-KR EUC-TW GB18030 GBK ISO-8859-1 ISO-8859-2 ISO-8859-3 ISO-8859-4 ISO-8859-5 ISO-8859-6 ISO-8859-7 ISO-8859-8 ISO-8859-9 ISO-8859-10 ISO-8859-11 ISO-8859-13 ISO-8859-14 ISO-8859-15 ISO-8859-16 KOI8-R KOI8-U Shift_JIS UTF-16BE UTF-16LE UTF-32BE UTF-32LE Windows-1251 IBM437 IBM737 IBM775 CP850 IBM852 CP852 IBM855 CP855 IBM857 IBM860 IBM861 IBM862 IBM863 IBM864 IBM865 IBM866 IBM869 Windows-1258 GB1988 macCentEuro macCroatian macCyrillic macGreek macIceland macRoman macRomania macThai macTurkish macUkraine stateless-ISO-2022-JP eucJP-ms CP51932 GB2312 GB12345 ISO-2022-JP ISO-2022-JP-2 CP50220 CP50221 Windows-1252 Windows-1250 Windows-1256 Windows-1253 Windows-1255 Windows-1254 TIS-620 Windows-874 Windows-1257 Windows-31J MacJapanese UTF-7 UTF8-MAC UTF8-DoCoMo SJIS-DoCoMo UTF8-KDDI SJIS-KDDI ISO-2022-JP-KDDI stateless-ISO-2022-JP-KDDI UTF8-SoftBank SJIS-SoftBank"
Aby sprawdzic kodowanie danego lancucha, korzystamy z metody ''encoding'':
ruby-1.9.2-p180 :003 > "this is a test".encoding
=> #
Aby przekonwertowac lancuch na inne kodowanie, korzystamy z metody ''encode'':
ruby-1.9.2-p180 :005 > "this is a test".encode("ISO-8859-2").encoding
=> #
Aby wlaczyc wsparcie kodowania znakow w samym kodzie zrodlowym, nalezy pierwsza (lub druga, jesli korzystamy z linii shebang) linie wypelnic tak:
# coding: utf-8