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.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
.
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
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 }
# 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
Mamy trzy rozne metody uruchamiania zewnetrznych programow z poziomu Rubego (podobnie jak w Perlu):
system
modulu Kernel
- idealna, gdy nie interesuje nas to co wyswietli program,``
- gdy potrzebujemy tego co program wyswietla,%x{}
- funkcjonalnie to odpowiednik backtickow.
Aby zakonczyc wykonywanie obecnego programu i uruchomic inny, skorzystac mozna z metody exec
.
exec "ruby another_script.rb" puts "To nigdy nie zostanie wyswietlone."
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!"
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.
Aplikacje czesto pracuja w srodowiskach i sytuacjach, w ktorych nie mozna zagwarantowac poprawnosci danych wejsciowych. Konieczne jest zabezpieczanie sie przeciw takim sytuacjom.
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.
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 | ![]() |
3 | ![]() |
4 | ![]() |
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.
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 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.
# 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
.
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 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 => #<Encoding:UTF-8>
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 => #<Encoding:ISO-8859-2>
Aby wlaczyc wsparcie kodowania znakow w samym kodzie zrodlowym, nalezy pierwsza (lub druga, jesli korzystamy z linii shebang) linie wypelnic tak:
# coding: utf-8