Spustit jako prezentaci

Znáte dobře Ruby?

aneb i Matz měl své slabé chvilky

David Majda

5. 11. 2007, Praha

Značení

Kód Výstup
puts "Hi!"

Značení

Kód Výstup
puts "Hi!"
Hi!

Přednáška je takový kvíz. Na slajdu vždy bude úsek kódu v Ruby a úkolem publika je říci, co tento kus kódu vypíše na výstup a proč. Po skupinovém přemítání přijde řešení a vysvětlení, proč kód dělá to, co dělá.

Prezentované úseky kódu jsou rozděleny do čtyř tématických celků.

Není-li uvedeno jinak, předpokládá se, že všechny příklady jsou v Ruby 1.8.6-p114.

Literály

1

Literály

puts 1

Literály

puts 1
1

Literály

puts 1
1
puts -(1)

Literály

puts 1
1
puts -(1)
-1

Literály

puts 1
1
puts -(1)
-1
puts -1

Literály

puts 1
1
puts -(1)
-1
puts -1
-1

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1
1

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1
1
puts -(1)

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1
1
puts -(1)
2

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1
1
puts -(1)
2
puts -1

Literály

class Fixnum
  def -@
    2 * self
  end
end
puts 1
1
puts -(1)
2
puts -1
-1
Po předefinování operátoru unární mínus pro třídu Fixnum se předefinovaná verze možná překvapivě ve třetím případě nepoužije. Je to proto, že pokud je znak "-" následován číslicí, je považován za součást následujícího čísla a je zpracován již parserem.

Literály

parse.y

numeric : tINTEGER
        | tFLOAT
        | tUMINUS_NUM tINTEGER         %prec tLOWEST
            {
                $$ = negate_lit($2);
            }
        | tUMINUS_NUM tFLOAT           %prec tLOWEST
            {
                $$ = negate_lit($2);
            }
        ;

Literály

puts +/-*/

Literály

puts +/-*/
(?-mix:-*)

Literály

puts +/-*/
(?-mix:-*)
puts /-*/

Literály

puts +/-*/
(?-mix:-*)
puts /-*/
(?-mix:-*)

Znaky +/-*/ se rozparsují jako literál regulárního výrazu, před nímž je unární operátor +. Tento operátor je v rámci optimalizace parserem odstraněn (parser předpokládá, že jeho aplikace je identita).

Toto chování zjevně není správně. V případě, kdy operátor není u dané třídy definován (což je i případ regulárního výrazu), by měla být vyhozena výjimka a v případě, že definován je, by se měla použít jeho definice a ne předpoklad, že je definován jako identita.

Chování jsem nahlásil jako bug, ale Matz ho za bug nepovažuje. Z verze 1.9 byla nicméně tato optimalizace odstraněna.

Literály

parse.y

arg     : tUPLUS arg
            {
                if ($2 && nd_type($2) == NODE_LIT) {
                    $$ = $2;
                }
                else {
                    $$ = call_op($2, tUPLUS, 0, 0);
                }
            }
        ;

Kde NODE_LIT může být:

  • Fixnum
  • Bignum
  • Symbol
  • Regexp

Literály

puts ''''''''

Literály

puts ''''''''
 

Literály

puts ''''''''
 
puts '' '' '' ''

Literály

puts ''''''''
 
puts '' '' '' ''
 

Literály

puts ''''''''
 
puts '' '' '' ''
 
puts 'ab' 'cd'

Literály

puts ''''''''
 
puts '' '' '' ''
 
puts 'ab' 'cd'
abcd

Literály

puts ''''''''
 
puts '' '' '' ''
 
puts 'ab' 'cd'
abcd
puts 'ab' \
     'cd' \
     'ef'

Literály

puts ''''''''
 
puts '' '' '' ''
 
puts 'ab' 'cd'
abcd
puts 'ab' \
     'cd' \
     'ef'
abcdef

Pokud se v Ruby nacházejí dva řetězce vedle sebe, jsou automaticky spojeny podobně jako v céčku. Spojení se provede již při parsingu, při každém průchodu tak zbytečně nevznikají objekty, které okamžitě zaniknou. Důvodem tohoto chování je samozřejmě optimalizace.

Metody a proměnné

2

Metody a proměnné

def a
  42
end

puts a

Metody a proměnné

def a
  42
end

puts a
42
Příkaz puts a zde zavolá metodu a a vypíše její výsledek. Nic objevného.

Metody a proměnné

def a
  42
end

a = 24

puts a

Metody a proměnné

def a
  42
end

a = 24

puts a
24

Zde dochází ke konfliktu mezi stejně nazvanou proměnnou a metodou. Ruby konflikt řeší tak, že přednost má proměnná.

Co ale způsobí to, že má proměnná přednost? Její pozdější definice? Samotný fakt její existence? Nebo něco jiného?

Metody a proměnné

a = 24

def a
  42
end

puts a

Metody a proměnné

a = 24

def a
  42
end

puts a
24
Na pořadí zřejmě nezáleží, proměnná má přednost vždy.

Metody a proměnné

if false
  a = 24
end

def a
  42
end

puts a

Metody a proměnné

if false
  a = 24
end

def a
  42
end

puts a
nil

Kupodivu nezáleží ani na tom, zda bylo přiřazení vůbec vykonáno – stačí, že se nachází někde v předcházejícím v kódu (ve stejném scope). Ruby si v tomto případě zapamatuje, že a je proměnná, ale protože ji ve skutečnosti nikde nepřiřazujeme hodnotu, příkaz puts vypíše nil.

Toto chování je poměrně hodně neintuitivní. Jeho důvodem je pravděpodobně snaha o optimalizaci (o tom, zda se zjišťuje hodnota proměnné nebo volá metoda, dokáže rozhodnout už parser). Zajímavé ale je, že v praxi problémy nezpůsobuje (alespoň jsem na to nikdy nenarazil ani o tom nečetl).

Jména tříd

3

Jména tříd

class C
  # ...
end

puts C.name

Jména tříd

class C
  # ...
end

puts C.name
C
Malé opakování: Metoda Class#name vypíše jméno třídy.

Jména tříd

module M
  class C
    # ...
  end
end

puts M::C.name

Jména tříd

module M
  class C
    # ...
  end
end

puts M::C.name
M::C
Vypsané jméno je plně kvalifikované.

Jména tříd

c = Class.new

puts c.name

Jména tříd

c = Class.new

puts c.name
 
A co taková anonymní třída? Její jméno je prázdný řetězec.

Jména tříd

C = Class.new

puts C.name

Jména tříd

C = Class.new

puts C.name
C
Situace se ovšem změní, pokud anonymní třídu přiřadíme do konstanty – v tom případě získá její jméno.

Jména tříd

c = Class.new
puts c.name

C1 = c
puts C1.name

C2 = c
puts C2.name

Jména tříd

c = Class.new
puts c.name

C1 = c
puts C1.name

C2 = c
puts C2.name

C1
C1
Anonymní třída může jméno získat kdykoliv po svém vytvoření, ne nutně hned. A jakmile už jméno má, nezmění ho.

Jména tříd

c = Class.new
puts c.name

$c = Class.new
puts $c.name

@c = Class.new
puts @c.name

@@c = Class.new
puts @@c.name

C = Class.new
puts C.name

Jména tříd

c = Class.new
puts c.name

$c = Class.new
puts $c.name

@c = Class.new
puts @c.name

@@c = Class.new
puts @@c.name

C = Class.new
puts C.name




C
Žádná jiná přiřazení než do konstanty "magická" nejsou.

Ekvivalence

4

Ekvivalence

f()   ⇔   self.f()

Otázka zní: Platí tato ekvivalence za všech okolností?

Ekvivalence

class C
  def f
    puts "Hi!"
  end

  def call_f1
    f
  end

  def call_f2
    self.f
  end
end

c = C.new
c.call_f1
c.call_f2

Ekvivalence

class C
  def f
    puts "Hi!"
  end

  def call_f1
    f
  end

  def call_f2
    self.f
  end
end

c = C.new
c.call_f1
c.call_f2
Hi!
Hi!

Ekvivalence

class C
  public
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2

Ekvivalence

class C
  public
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2
Hi!
Hi!
Explicitní označení metod za public nic nezmění, protože veřejné už stejně byly (není li viditelnost specifikována, předpokládá se public).

Ekvivalence

class C
  protected
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2

Ekvivalence

class C
  protected
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2
Hi!
Hi!
Změna viditelnosti metody f na protected opět nic nezmění – voláme ji z té samé třídy.

Ekvivalence

class C
  private
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2

Ekvivalence

class C
  private
    def f
      puts "Hi!"
    end

  public
    def call_f1
      f
    end

    def call_f2
      self.f
    end
end

c = C.new
c.call_f1
c.call_f2
Hi!
test.rb:13:in `call_f2':
private method `f' called
for #<C:0xb7d0b7d8>
(NoMethodError)
        from test.rb:19

Změna viditelnosti metody f na private už způsobí problém. Ruby totiž dovoluje volat privátní metody pouze bez explicitně uvedeného příjemce a je úplně jedno, že tento příjemce je self. Znamená to, že soukromí v Ruby je syntaktická, nikoliv sémantická záležitost. Jsem jediný, komu to přijde naprosto nekoncepční?

Ekvivalence f()self.f() tedy vždy neplatí. Uvedený příklad je nicméně jediná mě známá výjimka v celém Ruby.

Konec