The Object-Oriented Programming Language RubyDe Nederlandse Ruby Homepage




4. Variabelen, constanten en parameters



4.1 Maakt een toekenning een nieuwe copie van een object? 

Alle variabelen en constanten verwijzen naar een object. Uitzondering zijn niet geïnitialiseerde locale variabelen, die verwijzen nergens naar. Het gebruik van dat soort variabelen levert een NameError exceptie op. Bij het doen van een toekenning aan een variabele of het initialiseren van een constante laat je deze verwijzen naar het object van je keuze.

Het doen van een toekenning maakt dus nooit een nieuwe copie van een object.

Voor enkele speciale gevallen is er wat meer uitleg nodig. Instanties van Fixnum, NilClass, TrueClass, and FalseClass worden wel direct, dus zonder verwijzing, in variabelen en constanten gezet. Een variabele met als waarde het getal 42 of de constante true bevatten echt deze waarde en dus geen verwijzing. Toewijzing leid in dit geval dus tot een fysieke copie van deze objecten of types. Dit wordt uitgebreider bespoken in Immediate and Reference Objects.

4.2 Wat is de scope van een locale variabele?

Eeen nieuwe scope voor een locale variabele wordt gemaakt in (1) hoofdniveau (main), (2) een klasse (of module) definitie of (3) een methode definitie.

   
i  =  1       # (1)
class Demo
i = 2 # (2)
def meth
i = 3 # (3)
print "In method, i = ", i, "\n"
end
print "In class, i = ", i, "\n"
end
print "At top level, i = ", i, "\n"
Demo.new.meth

Produces:

In class, i = 2
At top level, i = 1
In method, i = 3

(Merk op de de klasse definitie uitvoerbare code is: de trace boodschap die de klasse bevat wordt geprint wanneer de klasse gedefinieerd word).

Een block (``{'' ... ``}'' or do ... end) introduceert bijna een nieuwe scope ;-) Locale variabelen binnen een block zijn niet toegengkelijk buiten het block. Echter, er wordt geen nieuwe locale variabele aangemaakt als een locale variabele binnen het block dezelfde naam heeft als een bestaande locale variabele binnen de scope van de aanroeper. De variabele is dan buiten het block te benaderen.

   
a = 0
1.upto(3) do |i|
a += i
b = i*i
end
a # -> 6
# b is not defined here

Dit wordt belangrijk bij threading: iedere thread krijgt zijn eigen copie van variabelen die locaal zijn t.o.v. het block van de thread:

   
threads = []

for name in ['one', 'two' ] do
threads << Thread.new {
localName = name
a = 0
3.times do |i|
Thread.pass
a += i
print localName, ": ", a, "\n"
end
}
end

threads.each {|t| t.join }

Produces:

onetwo: : 00

onetwo: : 11

onetwo: : 33

while, until, en for zijn control structures, geen blocks, dus locale variabelen binnen de structuur zijn toegangkelijk vanuit de omringende omgeving. loop echter  is a methode en het bijbehorende block introduceerd een nieuwe scope.

4.3 Wanneer is een locale variabelel toegangkelijk?

Eigenlijk is het beter om te vragen: "Wnneer bepaald Ruby dat iets een variabele is?" Het probleem ontstaat doordat de expressie ``a'' zowel een variabele als een methode aanroep zonder parameters kan zijn. Om een keuze te kunnen maken zoekt Ruby naar toekennings statements. Als op enig punt in de source code voorafgaand aan het gebruik van ``a'' een waarde aan ``a'' wordt toegekend wordt ``a''  gezien als variabele anders wordt het behandeld als een methode. Als voorbeeld hiervan een code fragment gemaakt door Clemens Hintze:

   
def a
print "Function 'a' called\n"
99
end

for i in 1..2
if i == 2
print "a=", a, "\n"
else
a = 1
print "a=", a, "\n"
end
end

Produces:

a=1
Function 'a' called
a=99

Tijdens het parsen ziet Ruby ``a'' gebruikt worden in het eerste print statement zonder vorafgaande toekenning en gaat er dus vanuit dat het een methode aanroep beteft. Bij het tweede print statement echter is Ruby een toekenning tegengekomen en behandeld ``a'' dus als een variabele.

Merk op dat de toekenning niet hoeft te zijn uitgevoerd: Ruby moet alleen een toekenning in de source gezien hebben. Dit programma veroorzaakt geen foutmelding:

   
a = 1 if false; a  # -> nil

Dit is normaal gesproken geen probleem. Mocht je er tegenaan lopen zet dan een toekenning zoals a = nil voor het eerste gebruik van de variabele. Dit heeft bovendien als voordeel dat het de toegangstijd voor locale variabelen die in loops worden gebruikt versneld.

4.4 Wat is de scope of a constante?

Een constante gedefinieerd in een klasse of module is direct bereikbaar binnen die definitie.

Constanten zijn direct toegangkelijk vanuit binnen liggende geneste klassen en modules.

Constanten in superklassen and ge-include modules zijn direct toegankelijk.

Afgezien van deze gevallen kunnen klasse en module constanten worden benaderd met behulp van de :: operator: ModuleNaam::CONST1 of KlasseNaam::CONST2.

4.5 Hoe worden parameters doorgegeven?

De actuele parameter word toegekend aan de formele parameter wanneer de methode wordt aangeroepen.(Zie toekenning voor meer over de semantiek van toekenningen)

   
  def addOne(n)
n += 1
end
a = 1
addOne(a) # -> 2
a # -> 1

Wanneer je object referenties doorgeeft is het mogelijk dat een methode de inhoud van een doorgegeven muteerbaar object wijzigt.

   
  def downer(string)
string.downcase!
end
a = "HELLO" # -> "HELLO"
downer(a) # -> "hello"
a # -> "hello"

Er is geen equivalent voor de pass-by-reference semantiek van andere programmeertalen..

4.6 Veranderd toekenning van een waarde aan een formele parameter de actuele parameter?

Een formele parameter is een locale variabele. Binnen een methode veranderd leidt een toekenning alleen tot het verwijzen naar een ander object.

4.7 Wat gebeurt er als ik een methode aanroep via aan formele parameter?

Alle Ruby variabelen (inclusief methode parameters) zijn verwijzingen naar objecte. Je kan methodes van deze objecten aanroepen om de staat van het object op te vragen of te wijzigen of om het object iets te laten doen. Dit kan je ook doen met objecten die als parameter aan methodes zijn doorgegeven.Voorzichtigheid is geboden wanner je dit doet omdat er sprake kan zijn van neven effecten die maken dat het programma moeilijk te volgen wordt.

4.8 Wat betekend het als een parameter vooraf word gegaan door ``*''?

Gebruikt als formele parameter maakt het sterretje het mogelijk een variabel aantal parameters door te geven aan een methode door ze te verzamelen in een array. Het array word dan toegekend aan de parameter met het sterretje.

   
def foo(prefix, *all)
for e in all
print prefix, e, " "
end
end

foo("val=", 1, 2, 3)

Produces:

val=1 val=2 val=3

Gebruikt in een methode aanroep expandeerd de * een array. Waarna de individuele elementen als parameter worden doorgegeven. arguments.

   
a = [1, 2, 3]
foo(*a)

Je kan een * zetten voor het laatste argument van:

  1. De linkerzijde van een meervoudige toekenning.
  2. De rechterzijde van een meervoudige toekenning.
  3. De formele parameterlijst van een methode.
  4. Actuele prarameters in een method aanroep.
  5. In de when clausule van een case structuur.

Bijvoorbeeld:

   
x, *y = [7, 8, 9]
x # -> 7
y # -> [8, 9]
x, = [7, 8, 9]
x # -> 7
x = [7, 8, 9]
x # -> [7, 8, 9]

4.9 Wat betekend het als een parameter vooraf word gegaan door ``&''?

Als de laatste formele parameter van een methode vooraf word gegaan door een ampersand word een block dat volgt op de methode aanroep omgezet in een Proc object en toegekend aan de laatste formele parameter.

Als de laatste formele parameter van een methode een Proc object is kan je die vooraf laten gaan door een ampersand om die om te zetten in een block. De methode kan dan yield gebruiken om het block aan te roepen.

   
square = proc { |i| i*i }

def meth1(&b)
print b.call(9), "\n"
end

meth1 { |i| i+i }

def meth2
print yield(8), "\n"
end

meth2 { |i| i+i }
meth2 &square

Produces:

18
16
64

4.10 Hoe kan ik een default waarde opgeven voor een formele parameter?

   
def greet(p1='hello', p2='world')
print "#{p1} #{p2}\n"
end

greet
greet "hi"
greet "morning", "mom"

Produces:

hello world
hi world
morning mom

De default waarde (die een willekeurige expressie mag zijn) word geëvalueerd op het moment dat de methode wordt aangeroepen. Voor de evaluatie word de scope van de methode gebruikt.

4.11 Hoe kan ik parameters doorgeven aan een block?

The formal parameters of a block appear between vertical bars at the start of the block:

   
proc { |a, b| a <=> b }

Deze parameters zijn locale variabelen. Als een locale variabele met dezelfde naam bestaat als het block word uitgevoerd zal die variabele gewijzigd worden door het aanroepen van het block. Mischien is dit iests goeds, misschien ook niet.
Typically, arguments are passed to a block using yield (or an iterator that calls yield), or by using the Proc.call method.

4.12 Waarom is mijn object onverwachts veranderd?

   
A = a = b = "abc"
b.concat("d") # -> "abcd"
a # -> "abcd"
A # -> "abcd"

Variabelen bevatten verwijzingen naar objecten.The toekenning A = a = b = "abc" zet een verwijzing naar de string ``abc'' intA, a, en b.

Als b.concat("d")aanroept, word de concat methode van de string ``abc'' aangeroepen. De string veanderd dan van ``abc'' in ``abcd''. Omdat  a en A verwijzen naar dat zelfde string object veranderd hun waarde eveneens.

In de praktijk is dit een minder groot probleem dan het hier lijkt.

Bovendien kunnen vanaf Ruby 1.5.2 alle objecten frozen worden, wat ze beschermd tegen verandering.

4.13 Kan de waarde van een constante ooit veranderen?

Een constante is een variabele waarvan de naam begint met een hoofdletter. In oudere Ruby implementaties werd er een waarschuwing gegeven als aan een constante een nieuwe waarde werd tegekend. In nieuwere Ruby versies kunnen, binnen instance methodes, constanten geen nieuwe waarde krijgen. Buiten dat kan de waarde altijd worden gewijzigd.