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!"
|
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
|
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 +/-*/
|
(?-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:
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é
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