Table of Contents
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 zwrocinil, bo to jest zawsze zwracane przez metodeputs,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
systemmoduluKernel- 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 | |
| 3 | |
| 4 | |
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 => #<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
