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 
end

W 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
end

Oraz 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

Posted in  | Tags  | 4 comments

Comments

  1. Avatar Michał Kwiatkowski said about 11 hours later:

    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.

    class X(object):
        def hello(self):
            return "Witaj"
    
    x = X()
    print x.hello() # => Witaj
    
    def bye(self):
        return "Żegnaj"
    X.bye = bye
    del bye
    
    print x.hello() # => Witaj
    
    x = X()
    print x.bye() # => Żegnaj
    print x.hello() # => Witaj

    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.

  2. Avatar MySZ said about 12 hours later:
    print $x->hello() # => Witaj
    
    class X { # => Parse error: syntax error, unexpected T_CLASS

    Błąd w składni – brak średnika. Dlatego nie spodziewał się klasy. Inaczej by krzyczał, że nie potrafi ponownie zdefiniować klasy o nazwie X.

  3. Avatar Jarosław Zabiełło said about 19 hours later:

    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…”.

  4. Avatar PaK said 1 day later:

    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

(leave url/email »)

   Comment Markup Help Preview comment