Ruby 1.9 wydany - opis głównych zmian i nowości
18 komentarzy | Kategorie: Ruby, Techblog | trackbackTagi: 1.9 release ruby ruby1.9 yarv
Niosę Wam nowinę: Ruby w wersji 1.9 już jest! Otóż zgodnie z obietnicą Matza, 24 grudnia została wydana kolejna odsłona tego języka. Być może wielu z Was nie zdaje sobie sprawy jak ważne jest to dla nas, programistów Rubiego. Dlatego chciałbym opisać jakie zmiany niesie ta wersja, jaki jest jej cel oraz podyskutować nad innymi aspektami tego wydania. Myślę że znajdą się tu także przydatne informacje dla tych, którzy do tej pory omijali język Ruby z jakiegoś powodu (np. braku jakiegoś elementu języka). Dowiecie się o zmianach w stosunku do poprzedniej wersji, a tych jest całkiem sporo. Niektóre są dosyć odważne, a nawet można by rzec, że ryzykowne.
Dla kogo Ruby 1.9?
Zanim przejdę do zmian, chciałbym omówić bardzo ważną kwestię. Tym bardziej, że panuje wiele odmiennych opinii. Chodzi o następujące kwestie: dla kogo przeznaczona jest wersja 1.9, czy jest przeznaczona do środowisk produkcyjnych, czy to tylko wersja developerska (rozwojowa)? Właściwe zrozumienie przez społeczność roli wersji 1.9 jest bardzo ważne dla dalszego rozwoju języka. Otóż Ruby 1.9 oznaczony jest jako 'development release', czyli jest to wersja rozwojowa. Może zawierać (i zawiera) błędy, nie wszystko działa tak jak powinno. Jednakże nie jest prawdą, że dopiero wersja 2.0 będzie wersją stabilną (tak napisał Jarosław Zabiełło na swoim blogu). Zapytałem o to na grupie ruby-talk by upewnić się i dostałem odpowiedź od Matz'a. Dowiedziałem się z niej, że zrezygnowano ze stylu *nixowego w oznaczaniu wersji i kolejne wersje 1.9.x będą w zamiarze stabilne, a więc przeznaczone do użycia produkcyjnego (tak jak wersje 1.8.x).
Ściągamy, kompilujemy i instalujemy
Zatem jest jeszcze za wcześnie by instalować wersję 1.9 jako podstawową. Jednakże polecam każdemu już ją zainstalować, obok wersji 1.8. Poniżej zamieszczam adresy ftp, skąd można ją pobrać:
Wersja binarne dla windows 32bit: ftp://ftp.ruby-lang.org/pub/ruby/binaries/mswin32/unstable/ruby-1.9.0-0-i386-mswin32.zip
Wersja binarne dla windows 64bit: ftp://ftp.ruby-lang.org/pub/ruby/binaries/mswin32/unstable/ruby-1.9.0-0-x64-mswin64_80.zip
Źródła do samodzielnego zbudowania: ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.0-0.tar.gz
Windows
By zainstalować interpreter pod tym systemem należy rozpakować archiwum .zip, proponuję katalog c:\ruby1.9), a następnie dodać katalog c:\ruby1.9\bin do zmiennej systemowej PATH (Mój komputer -> Właściwości -> Zaawansowane -> Zmienne środowiskowe). Jeśli posiadasz w systemie wersję 1.8, zwróć uwagę by ściezka do tej wersji była wcześniej niż do wersji 1.9. Jest to pierwszy krok by zapobiec konfliktowi tych wersji (wersja 1.8 powinna być wciąż domyślnie używana). Następnie zmień nazwy plików z katalogu c:\ruby1.9\bin\, dodając sufiks 1.9 (ruby.exe -> ruby1.9.exe). Zrób to dla wszystkich plików *.exe, *.bat. Dzięki temu wersja 1.9 będzie dostępna poprzez polecenie 'ruby1.9', a 1.8 tak jak do tej pory 'ruby'. Dodatkowo zrób kopię ruby1.9.exe -> ruby.exe, bo tego pliku używają bezpośrednio pliki .bat. Po tych krokach przetestuj czy wersje 1.8 i 1.9 działają poprawnie:
C:\>ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]
C:\>ruby1.9 -v
ruby 1.9.0 (2007-12-25 revision 14709) [i386-mswin32]
C:\>irb
>> RUBY_VERSION
=> "1.8.6"
>> exit
C:\>irb1.9
>> RUBY_VERSION
=> "1.9.0"
>> exit
Linux
Podobnie jak poprzednio chcemy zapobiec konfliktowi. Tutaj jest jeszcze prościej, bo podczas kompilacji użyjemy przełącznika --program-suffix. Jeśli chcesz to możesz zainstalować także interpreter bez praw roota - użyj wtedy opcji --prefix by określić katalog, do którego zostanie zainstalowany program. Oto przebieg przykładowej kompilacji i instalacji:
$ wget 'ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.0-0.tar.gz'
$ tar xvfz ruby-1.9.0-0.tar.gz
$ cd ruby-1.9.0-0
$ autoconf
$ ./configure --prefix=/home/radarek/opt/ --program-suffix=1.9
$ make
$ make rdoc
$ make install
$ ruby -v
ruby 1.8.6 (2007-06-07 patchlevel 36) [x86_64-linux]
$ ruby1.9 -v
ruby 1.9.0 (2007-12-28 revision 0) [x86_64-linux]
$ irb
>> RUBY_VERSION
=> "1.8.6"
>> exit
$ irb1.9
>> RUBY_VERSION
=> "1.9.0"
>> exit
Ruby.include?(YARV) #=> true
Dotychczasowe MRI (Matz Ruby Interpreter) obecnie zostało zintegrowane z YARV ("Yet Another Ruby Vm" albo "YARV Aint Ruby Vm"), czyli maszyną wirtualną. Nie będę się zagłębiał w zbędne (w tej chwili) szczegóły. Z punktu widzenia użytkownika (programisty) Rubiego, dostajemy do rąk około 3-4 razy szybszą implementację (chociaż niektóre operacje są takie same bądź nawet wolniejsze), optymalizacja operacji na liczbach oraz mniejsze zużycie pamięci. A z tego co wiem to nie jest ostatnie słowo. W przyszłości prawdopodobnie będzie można użyć pewnych podpowiedzi dla interpretera, które dadzą kolejnego kopa prędkości. Nie wybiegajmy jednak tak do przodu...;-) I zapamiętajcie: Ruby to YARV, YARV to Ruby. Z resztą YARV jest tylko nazwą kodową i lepiej pozostać przy dotychczasowym nazewnictwie.
Ruby.include?(Rubygems) && Ruby.include?(Rake) #=> true
Zarówno Rubygems jak i Rake zostały włączone do samego języka i nie trzeba ich już instalować osobno.
Matz (Yukihiro Matsumoto) i Ko1 (Sasada Koichi) - twórca i implementator (źródło - niestety po japońsku)
Zmiany
Przejdźmy do sedna sprawy - zmiany w samym języku. Tak jak wspomniałem wcześniej - jest ich dosyć sporo (nie wszystkie jestem w stanie tu zaprezentować), niektóre budzą obawy czy były potrzebne i czas je zweryfikuje. Jakkolwiek by nie było mnie odpowiada zdecydowanie twórcy języka (porównując do niezdecydowania twórców PHP i walka z kompatybilnością wstecz). Co ciekawe dzięki otwartym klasom i łatwości w tzw "monkey patching" przystosowanie aktualnych aplikacji do wersji 1.9 powinno być o wiele łatwiejsze (oczywiście autorzy bibliotek, gemów, frameworków powinni to zrobić w normalny sposób, czyli poprawić sam kod). Przykładowo jeśli usunięto jakąś metodę z klasy String, zawsze można dodać ją na własną rękę (klasy są otwarte, pamiętasz?).
Zmiany dotyczące składni i semantyki
- argumenty bloków są zawsze lokalne
i = 100
10.times {|i| }
puts i
#ruby1.8 => 9
#ruby1.9 => 100
- koniec z możliwością używania "dziwnych" zmiennych jako parametry bloków (@par1, $par2, par3['x'] nie są już poprawne, używamy więc zwykłych nazw zmiennych par1, par2 takich jak w metodach)
# poniższy kod nie zadziała w ruby 1.9 (za to w 1.8 tak)
h = {}
10.times {|h['a']| }
- lokalne zmienne w bloku - możemy po normalnych argumentach bloku wyspecyfikować zmienne lokalne dla bloku (po średniku), które przysłonią zmienne spoza bloku
a = 1
Proc.new {|v| a = v }.call(10)
puts a #=> 10
a = 1
Proc.new {|v; a| a = v }.call(10)
puts a #=> 1
- poprawiona składnia dla specyfikowania parametrów bloków i domknięć - parametrem może być także blok proc {|a, *rest, &block| }
p = Proc.new {|&b| b.call(:bar)}
p.call {|bar| puts bar }
- 'proc' jest synonimem Proc.new (do tej pory był synonimem 'lambda')
Klasa String
- obsługa różnych kodowań znaków (kodowanie pliku określa się tak jak w pythonie)
# -*- encoding: utf-8 -*-
s = "ąęćśńłóźż"
puts s.length #=> 9
puts s.bytes.to_a.size #=> 18
puts s.reverse #=> "żźółńśćęą"
puts s.split("").reverse.join #=> "żźółńśćęą"
puts s[0] #=> "ą"
puts s.encoding #=> zwraca obiekt kodowania, w tym wypadku Encoding:UTF-8
- String nie jest już więcej typem wyliczeniowym Enumerable. Jeśli chcesz iterować po stringu musisz jawnie określić czy iterujesz po znakach, bajtach czy też liniach:
# -*- encoding: utf-8 -*-
s = "ąęć\nśńł\nóźż"
puts "Kodowanie: #{s.encoding}" #=> UTF-8
puts "-" * 20, "| each_char ", "-" * 20
s.each_char do |c|
puts "znak: #{c}"
end
puts "-" * 20, "| each_byte ", "-" * 20
s.each_byte do |b|
puts "bajt: #{b}"
end
puts "-" * 20, "| each_line ", "-" * 20
s.each_line do |line|
puts "linia: #{line}"
end
- semantyka dla "string"[0]: zwraca jednoznakowy string, a nie kod znaku
- semantyka dla ?c, ?D: zwraca jednoznakowy string, a nie kod znaku
# -*- encoding: utf-8 -*-
puts "żaba"[1] #=> "a"
puts ?c #=> "c"
puts ?A #=> "A"
Operacje IO
- IO#getc zwraca jednoznakowy string, a nie kod znaku - do konwersji znak <=> liczba skorzystaj z metod Fixnum.chr oraz String#ord
puts "Podaj znak i naciśnij enter"
char = $stdin.getc
puts "wpisałeś: #{char}"
puts "char.ord: #{char.ord}"
puts 261.chr("utf-8") #=> "ą"
Nowa składnia dla haszy o kluczach będących symbolami
- hash {:ala => 1, :kot => 2} może być zapisany jako {ala: 1, kot: 2} (pamiętaj, że klucze są symbolami)
- nowa składnia pozwala symulować nazwane argumenty dla metod (coś jak keyword arguments w Pythonie)
def foo(args)
args.each do |key, value|
puts "#{key}: #{value}"
end
end
foo(ala: 1, kot: 2)
- można mieszać nowy i stary styl hashy:
{ala: 1, "kot" => 2} #=> {:ala=>1, "kot"=>2}
defined? i zmienne lokalne bloku
p = Proc.new {|a| puts defined?(a) }
p.call(1)
#ruby1.8 => local-variable(in-block)
#ruby1.9 => local-variable
Kernel#require i zmienna $"
- ścieżki plików dołączanych za pomocą require zapamiętywane są w zmiennej $" (alias $LOADED_FEATURES) jako bezwzględne - działa to mniej więcej tak jak poniższy kod:
$" << File.expand_path(loaded_file)
Module#instance_methods, Object#singleton_methods i inne z tej rodziny
- teraz zwracają tablicę symboli a nie stringów
class A
def foo
end
end
p A.instance_methods(false)
#ruby1.8 => ["foo"]
#ruby1.9 => [:foo]
Iteratory: Enumerable oraz Enumerable::Enumerator
- iteratory są zintegrowane z core (nie wymagają oddzielnego dołączania poprzez require) - iteratory pozwalają na równoległą iterację kilku kolekcji - jeśli iterator nie ma więcej elementów, a zostanie wywołana metoda 'next' to zostanie rzucony wyjątek StopIteration - pętla 'loop' potrafi przechwycić taki wyjątek i zakończyć działanie
e1 = [1, 2, 3, 4].each
e2 = [10, 11, 4].each
loop do
puts e1.next + e2.next
end
#11
#13
#7
- część metod modułu Enumerable zwraca iterator, jeśli nie podamy stowarzyszonego bloku. Dzięki temu można łączyć iteratory i tworzyć nowe własne
6.times.map {|e| e ** 2} #=> [0, 1, 4, 9, 16, 25]
- powyższy kod wydaje się oczywiście niezbyt praktyczny, ale idealnym przykładem łączenia iteratorów jest metoda 'with_index'. Jeśli brakowało Ci przy niektórych metodach dostępu do indeksu aktualnego elementu to nie musisz się już martwić. Metoda ta potrafi dodawać do parametrów iteracji aktualny indeks elementu:
a = [1, 2, 10, 3, 5, 4, 0]
# znajdź wszystkie elementy, których wartość nie przekracza indeksu
a.find_all.with_index {|e, i| e <= i }
[1, 2, 3].map.with_index {|e,i| e + i}
Operatory lambda ->, .()
- lambda, czyli domknięcie to jeden z podstawowych elementów języka. Właśnie zyskał nowy operator: -> (np: lambda {|name| puts "hello #{name}" } można zapisać jako ->(name){ puts "hello #{name}" })
- z początku wydaje się być dziwny, ale według mnie jest bardzo czytelny (po prostu jest to niespotykana do tej pory konstrukcja). Matz wybrał taki symbol nie bez przyczyny.
- po co ten operator? Dzięki niemu parametry domknięć mogą być tak samo definiowane jak parametry metod, czyli możemy np. ustawić wartości domyślne (do tej pory niemożliwe). Nowy operator był potrzebny gdyż używany do tej pory parser nie umożliwiał dodania tych elementów do istniejącej składni (|par1, par2=1|)
- Ruby 1.9 wprowadza także nowy sposób wywoływania: .()
add = ->(a, b) { puts "suma: #{a + b}" }
add.call(1, 2)
#lub
add.(3, 4)
hello = ->(name = "Matz"){ puts "Hello #{name}" }
hello.()
Wielokrotny operator unarny * (splat)
- przy wywoływaniu metody z wieloma argumentami możemy w ten sposób przesłać argumenty z wielu tablic (do tej pory tylko z jednej)
def foo(*a)
a
end
foo(1, *[2,3], 4, *[5,6]) # => [1, 2, 3, 4, 5, 6]
Obowiązkowe argumenty za opcjonalnymi
- pozwala na wyspecyfikowanie parametrów w metodzie, które są wymagane, za parametrami opcjonalnymi - jest to jeden z "ficzerów", który budzi wiele kontrowersji, jednakże nie będę tu teraz prowadzić wywodów czy ma sens czy nie.
def foo(a, b = 10, c)
p [a, b, c]
end
foo(1, 2) #a = 1, b = 10, c = 2
foo(1, 2, 3) #a = 1, b = 2, c = 3
def bar(a, b = 5, *c, d)
p [a, b, c, d]
end
bar(1, 2) #a = 1, b = 5, c = [], d = 2
bar(1, 2, 3) #a = 1, b = 2, c = [], d = 3
bar(1, 2, 3, 4) #a = 1, b = 2, c = [3], d = 4
BasicObject
- jest to klasa, z której dziedziczy klasa Object, a więc jest klasą podstawową (dotychczas Object) - jest to odchudzona wersja Object
Object.superclass #=> BasicObject
BasicObject.ancestors #=> [BasicObject]
Kontynuacje
- wciąż istnieją ale wymagają jawnego dołączenia: require 'continuation'
Fiber
- pozwala na pisanie "nieskończonych" generatorów - więcej w artykule "Ruby 1.9 adds Fibers for lightweight concurrency"
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y, x+y
end
end
20.times { puts fib.resume }
Method#receiver, Method#name, Method#owner
- receiver - odbiorca - owner - właściciel - name - nazwa metody
class Foo
def bar
puts "bar!"
end
end
o = Foo.new
m = o.method(:bar)
puts m.receiver
puts m.owner
puts m.name
m.()
Symbol#to_proc
- wypromowane przez rails, teraz wbudowane w Ruby core
a = ["a", "qwerty", "czx", "bb"]
puts a.sort_by {|e| e.length}
# jest równoważne
puts a.sort_by(&:length)
Nowy silnik wyrażeń regularnych - Oniguruma
- wprowadza rozszerzoną składnię wyrażeń regularnych, np. nazwane grupy (więcej tutaj)
text = "Yukihiro Matsuomoto"
m = text.match(/(?<first_name>\w+)\s+(?<last_name>\w+)/)
puts m["first_name"] #=> "Yukihiro"
puts m["last_name"] #=> "Matsumoto"
Wątki - Thread
- zrezygnowano z green-threads na rzecz wątków systemowych (OS threads), niestety wciąż wątki nie są odpalane na kilku procesorach jednocześnie (zdecydowano się na to m.in. by obecnie rozszerzenie w C działały bez zmian) - nie oznacza to że w przyszłości nie zmieni się to (warto przeczytać artykuł http://www.infoq.com/news/2007/05/ruby-threading-futures)
Podsumowanie
Zmiany zawsze niosą mieszane uczucia. Jedni polubią nową składnię (pamiętajmy, że opcjonalną) dla domknięć, inni będą na nią psioczyć. Mnie zaskoczył tylko jeden ficzer - foo(a, b = nil, c)... Mam nadzieję, że społeczność po prostu nie będzie tego używać i być może zostanie to jeszcze wycofane ;). Jakby nie było postęp jest ogromny. Przejście na maszynę wirtualną (szybkość!), obsługa różnych kodowań znaków, iteratory (naprawdę świetne!), nowy silnik regexpów, nowa składnia dla hashy (bardzo mi przypadła do gustu). A to tylko mały krok do wersji 2.0 więc czeka nas na pewno więcej ulepszeń. Niekompatybilności oczywiście są, ale tak jak wspomniałem w dużej mierze zostaje to uproszczone dzięki możliwościom samego języka (otwarte klasy, łatwość "monkey patching"). Nie ma co narzekać, tym bardziej, że patrząc na rozwój PHP wiemy co się dzieje gdy ciągle ogląda się do tyłu. Dlatego bądźmy dumni i wdzięczni Matzowi (oraz innym programistom Rubiego, a jest ich wielu) za dokonaną pracę. Tak trzymać Panowie :) (jeszcze nie wiem jak to będzie po japońsku :)).
Przydatne Linki
Na koniec garść linków:
RDoc dla wersji 1.9
Change in Ruby 1.9 - lista zmian, które zostały wyłapane z ChangeLoga, nie wszystkie już aktualne (w chwili gdy piszę te słowa strona nie działa, skorzystaj z cache)
plik ze spisem nowości i zmian (niestety bez opisu)
Ruby 2.0, Perl 6, Python 3.0, C++0x, D 2.0… dzieje się, oj dzieje :) Szkoda tylko, że zacząłem poznawać niecały miesiąc temu Ruby 1.8 a tu już nowa wersja… nie nadążę za tymi zmianami :( Ale w sumie zmiany bardzo mi się podobają, poza tymi argumentami domniemanymi. Ruby jest fajny :)
W przykładzie z Symobl.to_proc jest chyba błąd
puts a.sort_by(&;:length)
- nie powinno być średnika.
A poza tym artykuł wyśmienity :)
@apohllo, też to zauwazyłem. Ale to wina tego skryptu, który koloruje składnię. Jak wyłączysz js to będzie ok. Próbowałem na różne sposoby zapisać (encja & oraz poprzez #nn) ale ten sam efekt.
Jak się ma YARV do Rails i jego obecnej wersji ?
Heh, Matz mógłby dać możliwość użycia unikodowego znaku λ. :-)
render partial: ‘list’, link: λ(x) { some_url(x_id: x.id) }
Mnie by się taki kod bardzo podobał. :D
Z jednej strony bardzo podobają mi się rozszerzone iteratory czy też zapis { a: 1, b: 2 }, z drugiej jednak trochę boli mnie, że zaczynają się mnożyć różne metody na zrobienie tej samej rzeczy.
@pnowak: nie powiem Ci dokładnie, jednak dużo rzeczy jest już w tej chwili dostosowywanych do 1.9 i zapewne jest także z rails. Nie ma powodu, dla którego rails nie miałoby być dostosowane do 1.9, trzeba nam tylko czekać na wersję stabilną (prawdopobonie 1.9.1), ale to wymaga rzetelnego przetestowania przez społeczność. Dlatego zachęcam do testowania i zgłaszania bugów (http://rubyforge.org/projects/ruby/ -> bugs).
@mcv: pamiętaj że nie wszyscy używają unicode i lepiej żeby znaki spoza ascii nie były używane w języku :). Zawsze możesz na własną rękę zrobić sobie alias do ‘proc’ (to jest metoda). Pisałem o tym jakiś czas temu. Niestety -> jest już operatorem i nie da się go przeładować/zaliasować.
@mcv: http://www.oreillynet.com/ruby/blog/2007/10/fun_with_unicode_1.html
Że też Ci się chicało ;)
Kawał dobrej roboty :)
Bardzo fany artykul.
Panie Radku powiem tylko iz gleboko wierze w Rubinka. Jako byly juz programista Perl przeszedlem na strone pythona dopoki moja zona nie kupila mi ksiazki TAO RUBY. Juz dzis przepisanych zostalo kilka skryptow zarzadzajacych serwerem na Rubinka.
Wszystkim przeciwnikom Ruby mam do powiedzenia tylko tyle „Nikt was nie zmusza do programowania w Ruby”.
Co do Wersji D 2.0 :) masz racje sawyer zapowiada sie calkiem ciekawa zabawa.
qunis.
Znowu chce mi sie programowac.
Paweł: O, super! :D Widzę, że kluczem jest tu
Ku. :)>>
>>W przykładzie z Symobl.to_proc jest chyba >>błąd
>>puts a.sort_by(&;:length)
>>- nie powinno być średnika.
>>A poza tym artykuł wyśmienity :)
A ja myślałem ze to brud na moim monitorze :)
Podoba mi się tylko troche zawiodłem się że 1.9 .0 to nie jest jeszcze wersja produkcyjna, miałem takie nadzieje.
@JO, cierpliwości, nie od razu Rzym zbudowano :). Bardziej to ja się nie mogę doczekać Rubiniusa, smaka robią niezłego.
dzięki!
Fajny artykuł!
jak do joggera dodałeś kolorwanie składni ? to było razem z szablonem ?
@JO: to kwestia odpowiedniego skryptu w js + css. U mnie to działa w ten sposób, że skrypt po załadowaniu strony szuka wszystkich elementów „code” osadzonych w „pre” i je koloruje. Zobacz na źródła plików code_highlighter.js (ten trochę przerobiłem, żeby korzystał z jquery), languages.js. Do tego potrzebne są regułki css, niestety mam je wbite w jeden główny szablon. Jak chcesz to odezwij się na jabbera to Ci pomogę.
> po prostu jest to niespotykana do tej pory konstrukcja
ja osobiście widzę pewne podobieństwo do „haskellowej lambdy”, tzn. tam też używają „strzałki” z tym że (w przypadku wyrażenia lambda) w połączeniu z beksleszem, tzn.
\pattern1 pattern2 … patternn -> expression
Fajny artykuł, a co do ruby to jakoś mnie nie przekonuje...