Ruby, rozszerzanie klas i docstringi
Posted by Jarosław Zabiełło Thu, 13 Apr 2006 19:22:00 GMT
W jednym z wcześniejszych tekstów opisywałem zalety docstringów – wbudowanego mechanizmu dokumentacji w Pythonie.
W języku Ruby nie ma takiego mechanizmu. Jednak Ruby posiada unikalną cechę która pozwala go rozszerzać bez konieczności sięgania do języka C. Tą cechą są otwarte klasy.
class X
def hello
"Witaj"
end
end
x = X.new
puts x.hello # => Witaj
class X
def bye
"Żegnaj"
end
end
x = X.new
puts x.hello # => Witaj
puts x.bye # => Żegnaj
Coś takiego jest w ogóle nie do pomyślenia w językach tak mało dynamicznych jak Java czy C++ gdzie definicje klas są ustalane na poziomie kompilacji. W Ruby klasy są obiektami i są tworzone w momencie uruchomienia programu co daje nam w efekcie znacznie większy dynamizm i siłę ekspresji kodu.
Z kolei próba napisania czegoś podobnego w innych językach dynamicznych prowadzi także do zgoła kompletnie odmiennych zachowań. W przypadku PHP 5.1.2 poniższy kod wygeneruje błąd składni:
<?php
class X {
function hello() {
return "Witaj";
}
}
$x = new X();
print $x->hello() # => Witaj
class X { # => Parse error: syntax error, unexpected T_CLASS
function bye() {
return "Żegnaj";
}
}
?>Zaś w Pythonie poznikają nam definicje poprzednich metod (ale wcześniej zdefiniowane instancje klasy nie ulegną zmianie).
class X(object):
def hello(self):
return "Witaj"
x = X()
print x.hello() # => Witaj
class X(object):
def bye(self):
return "Żegnaj"
print x.hello() # => Witaj
x = X()
print x.bye() # => Żegnaj
print x.hello() # => AttributeError: X instance has no attribute 'hello'No dobrze, wróćmy do próby zaimplementowanie w Rubym czegoś podobnego do pythonowych docstringów. Załóżmy, że chcielibyśmy korzystać z następującej składni (poniższy kod jest oparty na PixAxe2, s.389):
class Przyklad
doc "To jest przykład doctringu"
# reszta kodu
endW związku z tym, że chcielibyśmy aby taka składnia działała dla każdego modułu i klasy, kod implementujący taki mechanizm powinniśmy zdefiniować w klasie Module.
class Module
@@docs = {}
def doc(s)
@@docs[self.name] = s.gsub(/^\s+/, '')
end
def Module::doc(aClass)
aClass = aClass.name if aClass.class <= Module
@@docs[aClass] || 'Nie ma dokumentacji'
end
endOraz przykład użycia:
class A
end
class B
doc "Przykład dokumentacji"
end
module C
doc <<-edoc
Dokumentacja
wielowierszowa
edoc
end
puts Module::doc(A) # => Nie ma dokumentacji
puts Module::doc(B) # => Przykład dokumentacji
puts Module::doc(C) #=> Dokumentacja\nwielowierszowa

Kanały IRC![[Dilber w Onecie]](/images/larry.png)


Twój przykład z Pythonem niczego nie udowadnia, bo to o czym piszesz jest możliwe w Pythonie, tylko że składnia jest inna.
To, co odróżnia Pythona od Ruby to właśnie ta możliwość zmiany klas wbudowanych. Ale dopóki mówimy o klasach definiowanych przez użytkownika, to ta różnica już nie występuje.
Błąd w składni – brak średnika. Dlatego nie spodziewał się klasy. Inaczej by krzyczał, że nie potrafi ponownie zdefiniować klasy o nazwie X.
Ja wiem, że tak można dodawać metody w Pythonie. Chodziło mi tylko o ciekawostkę jak na podobną składnię zareaguje Python i PHP. Co do błędu składni, to faktycznie brak średnika. Zmieni się tylko komunikat błędu na “PHP Fatal error: Cannot redeclare class x in…”.
Nie wiem jak jest w przypadku php 5.1 tej funkcjonalnosci moze juz nie byc, ale w php 4 mozna bylo osciagnac to za pomoca funkcji aggregate