Programmering i Ruby

Den Pragmatiske Programmerers Veiledning

Forrige < Innhold ^
Neste >

Språket Ruby



Dette kapittelet tar en titt på Ruby språket fra bunnen av og opp. I motsetning til de forutgående introduksjonskapitlene, vil vi her konsentrere oss om å presentere fakta i stedet for å gå inn på motivasjonen bak hvordan språket er designet. Vi ignorerer også de innebygde klassene og modulene dersom det er mulig. Disse dekkes i større detalj på side 275.

Dersom innholdet i dette kapittelet virker kjent, er det fordi det er meningen. Vi har dekket så å si alt av dette i de tidligere introduksjonskapitlene. Du kan anse dette kapittelet som en selvstendig referanse til kjernen i språket Ruby.

Kildekodeoppsett

Ruby programmer skrives i 7-bits ASCII. [ Ruby har også god støtte for Kanji via EUC, SJIS eller UTF-8 kodingssystemet. Dersom et annet kodesett enn 7-bits ASCII brukes, må KCODE-opsjonen settes til en passende verdi, slik som vist på side 137. ]

Ruby er et linjeorientert språk. Uttrykk og utsagn termineres i Ruby ved slutten av linjen med mindre utsagnet er åpenbart uferdig---for eksempel dersom det siste elementet på en linje er en operator eller et komma. Semikolon kan brukes for å separere mellom flere uttrykk på en linje. Du kan også sette en backslash på slutten av en linje for å kunne fortsette på neste linje. Kommentarer starter med `#' og går til slutten av den fysiske linjen. Kommentarer ignoreres ved kompilering.

a = 1

b = 2; c = 3

d = 4 + 5 +      # no '\' needed     6 + 7

e = 8 + 9   \     + 10         # '\' needed

Fysiske linjer mellom en linje som begynner med =begin og en linje som begynner med =end ignoreres av kompilatoren og kan brukes for å skrive innebakt dokumentasjon (se vedlegg A, som starter på side 511).

Ruby leser sitt programinnput i en gjennomgang, så du kan sende programmer direkte til kompilatorens stdin.

echo 'print "Hello\n"' | ruby

Dersom kompilatoren kommer over en linje i kildekoden som bare inneholder ``__END__'', uten mellomrom hverken foran eller bak, behandler Ruby linjen som slutten på programmet---eventuelle linjer som følger etter denne vil ikke bli kompilert. Men du kan laste disse linjene inn i det kjørende programmet ved å bruke det globale IO-objektet DATA, som er beskrevet på side 217.

BEGIN og END blokker

Alle Ruby kildekode-filer kan deklarere kodeblokker som skal kjøres når filen blir lastet (BEGIN-blokkene) og etter at programmet er ferdig (END-blokkene).

BEGIN {
  begin kode
}

END { end kode }

Et program kan inneholde flere BEGIN og END blokker. BEGIN-blokker utføres i rekkefølgen de dukker opp under kompilering mens END-blokker utføres i motsatt rekkefølge.

Generell avgrenset innput

Det finnes flere alternative måter for å skrive strenger, tabeller, regulære uttrykk og skallkommandoer ordrett inn i kildekoden som literaler ved hjelp av en generell avgrenset syntaks. Alle disse formene starter med et prosenttegn fulgt av et tegn som identifiserer typen til literalen. Disse tegnene summeres opp i tabell 18.1 på side 200; de faktiske literalene beskrives i egne avsnitt senere i dette kapittelet. Disse tegnene

Generell avgrenset innput
Type Mening Se side
%q Enkelt-sitert streng 202
%Q, % Dobbelt-sitert streng 202
%w En Array med elementer 204
%r Regulært uttrykk 205
%x Skallommando 218

Etter tegnet som antyder typen følger et avgrensningstegn som kan være hvilket som helst tegn. Dersom avgrensningstegnet er blant tegnene ``('', ``['', ``{'' eller ``<'' vil literalen bestå av tegnene opp til det passende lukkende avgrensningstegnet, med nøstede avgrensingspar tatt i betraktning. For alle andre avgrensningstegn vil literalen bestå av tegnene frem til neste gang avgrensningstegnet forekommer.

%q/this is a string/
%q-string-
%q(a (nested) string)

Avgrensede strenger kan fortsette over flere linjer.

%q{def fred(a)
     a.each { |i| puts i }
   end}

De grunnleggende typene

De grunnleggende typene i Ruby er numre, strenger, tabeller, hashtabeller, rekker, symboler og regulære uttrykk.

Hel- og flyttall

Heltall er i Ruby objekter av klassen Fixnum eller Bignum. Fixnum-objekter holder heltall som får plass inne i maskinarkitekturens ord (word) minus 1 bit. Når et Fixnum går utover denne rekkevidden, omdannes det automatisk til et Bignum-objekt, som har en rekkevidde som kun bekrenses av mengden tilgjengelig minne. Dersom en operasjon med et Bignum resultat har en endelig verdi som passer inn i en Fixnum, vil resultatet komme ut som en Fixnum.

Heltall skrives med et valgfritt pluss- eller minustegn, en valgfri baseindikator (0 for oktal, 0x for heksidesimal eller 0b for binært), fulgt av en streng av sifre i den angitte basen. Understrekningstegn i strengen ignoreres. for binary),

Du kan få tak i ASCII heltallsverdien til et tegn ved å plassere et spørsmålstegn foran. Kontroll- og metakombinasjoner av tegn kan også genereres ved å bruke ?\C-x, ?\M-x og ?\M-\C-x. Kontrollversjonen av ch er ch&0x9f og metaversjonen er ch | 0x80. Du kan få tak i heltallsverdien til et backslashtegn ved sekvensen ?\\.

123456                    # Fixnum
123_456                   # Fixnum (understrekningstegn ignoreres)
-543                      # Negativ Fixnum
123_456_789_123_345_789   # Bignum
0xaabb                    # Heksadecimal
0377                      # Oktal
-0b1010                   # Binært (negetert)
0b001_001                 # Binært
?a                        # tegnkode
?A                        # stor bokstav
?\C-a                     # kontroll a = A - 0x40
?\C-A                     # hvorvidt det er store eller små bokstaver ignoreres for kontrolltegn
?\M-a                     # meta setter bit 7
?\M-\C-a                  # meta og kontroll a

En numerisk literal med et desimaltegn og/eller en eksponent blir gjort om til et Float-objekt som tilsvarer den underliggende arkitekturens double datatype. Du må ha et siffer etter desimaltegnet, siden 1.e3 vil forsøke å kalle metoden e3 i klassen Fixnum.

12.34 » 12.34
-.1234e2 » -12.34
1234e-2 » 12.34

Strenger

Ruby tilbyr en mengde mekanismer for å lage strenger ordrett i kildekoden. Hver av dem genererer objekter av typen String. Mekanismene varierer i hvordan strengen avgrenses og hvor stor grad av substitusjon som gjøres på innholdet.

Enkelt-siterte strenger (' stuff ' og %q/stuff/) gjennomgår minst substitusjon. Begge versjonene endrer sekvensen \\ til en enkelt backslash og formen med apostrof konverterer \' til en enkelt apostrof.

'hello' » hello
'a backslash \'\\\'' » a backslash '\'
%q/simple string/ » simple string
%q(nesting (really) works) » nesting (really) works
%q no_blanks_here ; » no_blanks_here

Dobbelt-siterte strenger ("stuff", %Q/stuff/ og %/stuff/) utsettes for ytterlige substitusjoner som vist i tabell 18.2 på side 203.

Substitusjon i dobbelt-siterte strenger

\a Bjelle/alarm (0x07) \nnn Oktal nnn
\b Slettetast/backspace (0x08) \xnn Heksidesimal nn
\e Escape (0x1b) \cx Control-x
\f Formfeed (0x0c) \C-x Control-x
\n Linjeskift/newline (0x0a) \M-x Meta-x
\r Return (0x0d) \M-\C-x Meta-control-x
\s Mellomrom/space (0x20) \x x
\t Tab (0x09) #{expr} Verdien til expr
\v Vertikal tab (0x0b)

a  = 123
"\123mile" » Smile
"Say \"Hello\"" » Say "Hello"
%Q!"I said 'nuts'," I said! » "I said 'nuts'," I said
%Q{Try #{a + 1}, not #{a - 1}} » Try 124, not 122
%<Try #{a + 1}, not #{a - 1}> » Try 124, not 122
"Try #{a + 1}, not #{a - 1}" » Try 124, not 122

Strenger kan gå over flere linjer og vil da inneholde linjeskifttegn. Du kan også bruke here-dokumenter til å uttrykke lange strengliteraler. Når Ruby parser sekvensen <<identifikator eller <<sitert streng bytter den det ut med en strengliteral bygd opp fra påfølgende logiske innputlinjer. Ruby avslutter oppbyggingen av strengen når den finner en linje som starter med identifikatoren eller den siterte strengen. Du kan plasere et minustegn umiddelbart etter << tegnene for å tillate at termineringslinjen kan indenteres fra venstre kant. Dersom en sitert streng ble brukt for å spesifisere termineringssekvensen, vil dens siteringsregler gjelde for here-dokumentet, hvis ikke vil reglene for dobbelt-siterte strenger gjelde.

a = 123
print <<HERE
Double quoted \
here document.
Sum = #{a + 1}
HERE

print <<-'THERE'     This is single quoted.     The above used #{a + 1}     THERE
produserer:
Double quoted here document.
Sum = 124
    This is single quoted.
    The above used #{a + 1}

Enkelt- og dobbelt-siterte strenger som kommer om hverandre i innputen blir slått sammen til å utgjøre et enkelt String-objekt.

'Con' "cat" 'en' "ate" » "Concatenate"

Strenger lagres som sekvenser av 8-bits bytes, [ For bruk i Japan støtter jcode-biblioteket et sett av operasjoner på strenger skrevet med EUC, SJIS eller UTF-8 koding. Den underliggende strenger blir dog fremdeles aksessert som en serie av bytes.] og hver byte kan inneholde hvilken som helst av de 256 8-bits verdiene, inklusive null og linjeskift. Substitusjonsmekasnismene i tabell 18.2 på side 203 tillater enkelt og portabel innsetting av tegn som ikke kan printes.

Hver gang en strengliteral brukes i en tilordning eller som en parameter blir et nytt String-objekt laget.

for i in 1.
  print 'hello'.id, " "
end
produserer:
537763324 537763034 537763004

Dokumentasjonen for klassen String starter på side 363

Rekker

Utenfor konteksten til en betingelse konstruerer expr .. expr og expr ... expr Range-objekter. Varianten med to punktum er en inklusiv rekke mens varianten med tre punktum er en rekke som ekskluderer det siste elementet. Se beskrivelsen av klassen Rangeside 359 for ytterlige detaljer. Se også beskrivelsen av betingelsesuttrykk på side 222 for andre bruksområder av rekker.

Tabeller

Literaler av klassen Array lages ved å skrive en liste av objektreferanser separert med komma mellom firkantparenteser. Et eventuelt ekstra kommategn på slutten ignoreres.

arr = [ fred, 10, 3.14, "This is a string", barney("pebbles"), ]

Tabeller med strenger kan konstrueres enklere med en spesiell notasjon %w. Denne notasjonen deler opp ved mellomrom slik at man får en tabell med ordene som elementer. Et mellomrom kan escapes med en backslash. Dette er en generell avgrenset form for innput som beskreves på sidene 200--201.

arr = %w( fred wilma barney betty great\ gazoo )
arr » ["fred", "wilma", "barney", "betty", "great gazoo"]

Hashtabeller

En literal Hash skrives som en liste av nøkkel/verdi-par mellom krøllparenteser, med enten et komma eller sekvensen => mellom nøkkelen og verdien. Et eventuelt ekstra komma på slutten ignoreres.

colors = { "red"   => 0xf00,
           "green" => 0x0f0,
           "blue"  => 0x00f
         }

Det er ikke nødvendig for nøklene og/eller verdiene i en bestemt hashtabell at de har samme type.

Krav til en hashtabellnøkkel

Det eneste begrensningen for et objekt som skal brukes som nøkkel i en hashtabell er at det svarer på meldingen hash med en hashverdi og hashverdien for en gitt nøkkel må ikke endre seg. Det betyr at visse klasser (slik som Array og Hash i skrivende stund) ikke kan brukes som nøkler uten videre, fordi hashverdiene deres endrer seg ut fra innholdet.

Dersom du holder en ekstern referanse til et objekt som brukes som en nøkkel og bruker den referansen til å endre objektet og dermed dets hashverdi, kan det være at hashoppslag på nøkkelen ikke vil fungere.

Siden strenger både er den mest brukte formen for nøkler og ofte endrer innhold, behandler Ruby strengnøkler spesielt. Dersom du bruker et String-objekt som en nøkkel vil hashtabellen duplisere strengen internt og bruke den kopien som sin nøkkel. Eventuelle endringer gjort på den opprinnelige strengen i etterkant vil ikke påvirke hashtabellen.

Dersom du skriver dine egne klasser og bruker instanser av dem som nøkler i en hashtabell må du være sikker på at enten (a) hashverdiene til nøkkelobjektene ikke endrer seg etter at objektene har blitt laget eller (b) du husker å kalle metoden Hash#rehash for å reindeksere en hashtabell når hashverdien til en nøkkel endres.

Symboler

Et symbol i Ruby er den interne representasjonen av et navn. Du kan konstruere symbolet til et navn ved å sette et kolontegn foran navnet. Et gitt navn vil alltid generere det samme symbolet, uavhengig av hvordan navnet brukes innad i programmet.

:Object
:myVariable

Andre språk kaller denne prosessen for ``internering'' og kaller symboler for ``atomer.''

Regulære uttrykk

Regulære uttrykk skrevet rett i kildekoden er objekter av typen Regexp. De kan lages ved å eksplisitt kalle konstruktøren Regexp.new eller ved å bruke de literale formene /mønster/ og %r{ mønster }. Varianten med %r er en form for generell avgrenset innput (beskrevet på sidene 200--201).

/pattern/
/pattern/options
%r{pattern}
%r{pattern}options
Regexp.new( 'pattern' [, options
            ] )

Opsjoner til regulære uttrykk

Et regulært uttrykk kan inkludere en eller flere opsjoner som endrer hvordan mønsteret tolkes ved sammenligning med strenger. Dersom du bruker literaler for å lage Regexp-objektet utgjør opsjonene en eller flere tegn plassert rett etter det terminerende tegnet. Dersom du derimot bruker Regexp.new, gir du opsjonene ved hjelp av den andre parameteren til konstruktoren og en håndfull konstanter.

i Case Insensitive. Mønsteret vil ikke gjøre forskjell på store og små bokstaver. Sammenligninger fungerer også slik dersom den globale variabelen $= er satt.
o Substitute Once. Eventuelle #{...} substitusjoner i literalen for et gitt regulært uttrykk vil bare bli utført en gang når det først evalueres. Hvis ikke vil substitusjonene bli utført hver gang literalen genererer et Regexp-objekt.
m Multiline Mode. Vanligvis slår tegnet ``.'' ut på alle tegn, med unntak av linjeskift. Med opsjonen /m slår ``.'' ut på alle tegn.
x Extended Mode. Kompliserte regulære uttrykk kan være vanskelige å lese. Opsjonen `x' lar deg legge til mellomrom, linjeskift og kommentarer i mønsteret for å gjøre det lettere å lese.

Mønstre i regulære uttrykk

vanlige tegn
Alle tegn, med unntak av ., |, (, ), [, \, ^, {, +, $, * og ?, slår ut på seg selv. For å treffe på et av disse spesielle tegnene, plasser en backslah foran tegnet.

^
Slår ut på begynnelsen til en linje.

$
Slår ut på slutten av en linje.

\A
Slår ut på begynnelsen av strengen.

\z
Slår ut på slutten av strengen.

\Z
Slår ut på slutten av strengen med mindre strengen slutter med et ``\n''-tegn, da slår det ut rett før ``\n''-tegnet.

\b, \B
Slår ut på respektivt ord- og ikke-ord-grenser.

[ tegn ]
En tegnklasse slår ut på hvilket som helst av tegnene mellom firkantparentesene. Tegnene |, (, ), [, ^, $, *, og ? som har spesiell betydning andre steder i mønstre, mister deres særegne signifikans mellom firkantparenteser. Sekvensene \ nnn, \x nn, \c x, \C- x, \M- x og \M-\C- x fungerer som vist i tabell 18.2 på side 203. Sekvensene \d, \D, \s, \S, \w og \W er kortformer for grupper av tegn, som vist i tabell 5.1 på side 59. Sekvensen c1-c2 representerer alle tegn fra og med c1 til og med c2. Literale ] eller - tegn må komme umiddelbart etter den åpnende firkantparentesen. En oppoverpil (^) umiddelbart etter den første åpnende firkantparentesen negeterer betydningen til mønsteret---det slår ut på et hvert tegn som ikke er i tegnklassen.

\d, \s, \w
Dette er kortformer for tegnklasser som slår ut på respektivt sifre, mellomromstegn og ordbokstaver. De negeterte formene \D, \S, and \W slår ut på tegn som ikke er sifre, mellomromstegn eller ordbokstaver. Disse forkortelsene summeres i tabell 5.1 på side 59.

. (punktum)
Utenfor firkantparenteser slår punktum ut på et hvert tegn med unntak av linjeskift. (Med /m-opsjonen slår den også ut på linjeskift).

re *
Slår ut på null eller flere forekomster av re.

re +
Slår ut på en eller flere forekomster av re.

re {m,n}
Slår ut på minst ``m'' og maksimalt ``n'' forekomster av re.

re ?
Slår ut på null eller ingen forekomst av re. Modifikatorene *, + og {m,n} er grådige til vanlig. Legg til et spørsmålstegn for å gjøre dem slik at de slår ut på minst mulig.

re1 | re2

(...)
Parenteser brukes for å gruppere regulære uttrykk. For eksempel slår mønsteret /abc+/ ut på en streng som inneholder en ``a,'' en ``b,'' og en eller flere``c''-er. Derimot slår /(abc)+/ ut på en eller flere sekvenser av ``abc''. Parenteser brukes også for å samle resultatene fra sammenligningene. For hver åpnende parentes lagrer Ruby resultatet til delen av sammenligningen mellom den og den korresponderende lukkende parentesen som påfølgende grupper. Inne i samme mønster referer \1 til hva den første gruppen slo ut på, \2 hva den andre gruppen slo ut på og så videre. På utsiden av mønsteret har de spesielle variablene $1, $2 og så videre, samme nytten.

Substitusjoner

#{...}
Utfører et uttrykkssubstitusjoner, akkurat som med med strenger. Vanligvis blir substitusjonen utført hver gang literalen til et regulært uttrykk evalueres. Med /o-opsjonen utføres substitusjonen kun første gang.

\0, \1, \2, ... \9, \&, \`, \', \+
Substituerer inn delresultatet til det nte grupperte subuttrykket, hele resultatet, delen før utslaget, delen etter utslaget eller den høyeste gruppen.

Utvidelser

I likhet med Perl og Python tilbyr Ruby sine regulære uttrykk noen utvidelser over de tradisjonelle regulære uttrykkene til Unix. Alle utvidelsene skrives inn mellom tegnene (? og ). Parentesene som avgrenser disse utvidelsene er grupper men de genererer ikke tilbakereferanser: de tilordner ikke verdier til \1 og $1 etc.

(?# kommentar)
Setter inn en kommentar i mønsteret. Innholdet ignoreres under sammenligning.

(?:re)
Grupperer re uten å generere tilbakereferanser. Dette er ofte nytting når du trenger å gruppere flere konstruksjoner men ikke ønsker at gruppen skal tilordne en verdi til $1 eller lignende. I eksempelet som følger slår begge mønstrene ut på en dato med enten kolon eller mellomrom mellom måneden, dagen og året. Den første varianten lagrer separeringstegnene i $2 og $4, mens den andre varianten ikke lagrer separatorene i en ekstern variabel.

date = "12/25/01"
date =~ %r{(\d+)(/|:)(\d+)(/|:)(\d+)}
[$1,$2,$3,$4,$5] » ["12", "/", "25", "/", "01"]
date =~ %r{(\d+)(?:/|:)(\d+)(?:/|:)(\d+)}
[$1,$2,$3] » ["12", "25", "01"]

(?=re)
Slår ut på re på dette punktet men forbruker ikke det den slår ut på (dette er også kjent som ``zero-width positive lookahead''). Dette lar deg se framover i strengen etter konteksten til en sammenligning uten å påvirke $&. I dette eksempelet slår scan metoden ut på ord som følges av et komma, men selve kommategnene blir ikke inkludert i resultatet.

str = "red, white, and blue"
str.scan(/[a-z]+(?=,)/) » ["red", "white"]

(?!re)
Slår ut hvis re ikke slår ut på dette punktet. Forbruker ikke det som passer (zero-width negative lookahead). For eksempel passer /hot(?!dog)(\w+)/ på et hvert ord som inneholder bokstavene ``hot'' som ikke følges av ``dog'' og ved sammenligning returneres slutten av ordet i $1.

(?>re)
Nøster et uavhengig regulært uttrykk inne i det første regulære uttrykket. Dette uttrykket er ankret fast til den gjeldende utslagsposisonen. Dersom uttrykket forbuker tegn vil disse ikke lenger være tilgjengelig for det regulære uttrykket som er på et høyere nivå. Derfor kan man ikke gå tilbake når man bruker denne konstruksjonen, som kan hjelpe på ytelse. For eksempel tar mønsteret /a.*b.*a/ eksponensiell tid når man sammenligner med en streng som har en ``a'' fulgt av en mengde ``b''-er, uten en avsluttende ``a.'' Men dette kan unngås dersom man bruker et nøstet regulært uttrykk som /a(?>.*b).*a/. I denne formen vil det nøstede uttrykket spise opp alt den kan i fra innputstrenget opp til den siste ``b.'' Når sjekken for en etterfølgende ``a'' så feiler, er det ikke nødvendig å gå tilbake og sammenligningen feiler umiddelbart.

require "benchmark"
include Benchmark
str = "a" + ("b" * 5000)
bm(8) do |test|
  test.report("Normal:") { str =~ /a.*b.*a/ }
  test.report("Nested:") { str =~ /a(?>.*b).*a/ }
end
produserer:
              user     system      total        real
Normal:   4.170000   0.000000   4.170000 (  4.150970)
Nested:   0.000000   0.000000   0.000000 (  0.001529)

(?imx)
Slår på den korresponderende ``i'', ``m'' eller ``x'' opsjonen. Dersom den brukes i en gruppe er effekten begrenset til gruppen.

(?-imx)
Slår av ``i'', ``m'' eller ``x'' opsjonen.

(?imx:re)
Slår på ``i'', ``m'' eller ``x'' opsjonen for re.

(?-imx:re)
Slår av ``i'', ``m'' eller ``x'' opsjonen for re.

Navn

Navn brukes i Ruby for å referere til konstanter, variabler, metoder, klasser og moduler. Det første tegnet i et navn hjelper Ruby til å avklare hvordan det er ment å brukes. Visse navn, som listet i tabell 18.3 på side 210, er reserverte ord og bør ikke brukes som navn på en variabel, metode, klasse eller modul.

Reserverte ord

__FILE__ and def end in or self unless
__LINE__ begin defined? ensure module redo super until
BEGIN break do false next rescue then when
END case else for nil retry true while
alias class elsif if not return undef yield

I disse beskrivelsene refererer liten bokstav tegnene ``a'' til og med ``z'', så vel som ``_'', understrekningstegnet. Stor bokstav vil si ``A'' til og med ``Z''. Siffer vil si ``0'' til og med ``9.'' Navnebokstaver betyr en hver kombinasjon av små og og store bokstaver og sifre.

Navnet til en lokal variabel består en liten bokstav, fulgt av navnebokstaver.

fred  anObject  _x  three_two_one

Navnet til en instansvariabel starter med et ``at''-tegn (``@'') fulgt av en stor eller liten bokstav, og eventuelt ytterlige navnebokstaver.

@name  @_  @Size

Navnet til en klassevariabel starter med to ``at''-tegn (``@@'') fulgt av en stor eller liten bokstav, og eventuelt ytterlige navnebokstaver.

@@name  @@_  @@Size

En konstants navn starter med en stor bokstav fulgt av navnebokstaver. Klassenavn og modulnavn er konstanter og følger navnekonvensjonen for dem. For konstante variabler er konvensjonen at man vanligvis skriver hele navnet med store bokstaver og understrekningstegn.

module Math
  PI = 3.1415926
end
class BigBlob

Globale variabler, samt noen spesielle systemvariabler, starter med et dollartegn (``$'') fulgt av navnebokstaver. I tillegg er det et sett med to-tegns variabelnavn hvor det andre tegnet er et punktueringstegn. Disse predefinerte variablene listes opp på side 213. Til slutt nevner vi at variabelnavnet til en global variabel kan formes ved å bruke ``$-'' fulgt av et hvert enkelt tegn.

$params  $PROGRAM  $!  $_  $-a  $-.

Metodenavn beskrives i avsnittet som starter på side 225.

Tvetydighet mellom variabler og metoder

Når Ruby ser et navn slik som ``a'' i et uttrykk må det finne ut hvorvidt det referer til en lokal variabel eller er et kall til en metode uten parametre. For å finne ut av dette bruker Ruby en heuristikk. Mens Ruby-fortolkeren leser en kildekodefil, holder den oversikt over symboler den har gjort tilordninger til. Den antar at disse symbolene er variable. Når den deretter kommer over et symbol som enten kan være en variabel eller et metodekall, sjekker den om den har sett en forutgående tilordning. Dersom det var en tilordning, behandles symbolet som en variabel. Var det derimot ikke noen tilordning, behandles symbolet som et metodekall. For et mer patologisk eksempel på dette, ta en titt på følgende kode som ble sendt inn av 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
produserer:
a=1
Function 'a' called
a=99

Under tolkingen ser Ruby bruken av ``a'' i den første utskriftskommandoen og, siden den ikke har sett noen tilordning til ``a'', antar at det er et metodekall. Når fortolkeren så kommer til den andre utskriftskommandoen derimot, har den sett en tilordning og behandler derfor ``a'' som en variabel.

Legg merke til at tilordningen ikke må utføres---Ruby må bare ha sett den. Følgende program feiler ikke.

a = 1 if false; a

Variabler og konstanter

Variabler og konstanter i Ruby holder referanser til objekter. Variablene selv har ikke noen reell type. I stedet er typen til variabelen definert utelukkende av hvilke beskjeder objektet svarer på. [Når vi sier at en variabel ikke har en type mener vi at enhver variabel kan på forskjellige tidspunkt holde referanse til objekter av mange forskjellige typer.]

I Ruby er en konstant også en referanse til et objekt. Konstanter lages når de først bilr tilordnet (vanligvis i en klasse- eller moduldefinisjon). Ruby, i motsetning til mindre fleksible språk, lar deg endre verdien til en konstant, selv om dette vil føre til en advarsel.

MY_CONST = 1
MY_CONST = 2   # generates a warning
produserer:
prog.rb:2: warning: already initialized constant MY_CONST

Legg merke til at selv om konstanter ikke bør endres, kan du fritt endre den interne tilstanden til objektene de refererer til.

MY_CONST = "Tim"
MY_CONST[0] = "J"   # endrer streng som referes til av en constant
MY_CONST » "Jim"

Tilordning kan potensielt lage aliaser til objekter, slik at samme objekt kan nås med forskjellige navn.

Konstanters og variablers skop

Konstanter som defineres inne i en klasse eller modul kan aksesseres uten ekstra bry. På utsiden av klassen eller modulen kan de aksesseres ved hjelp av skop-operatoren; ``::'' med et uttrykk foran som returnerer modulen eller klassen det er snakk om. Konstanter utenfor en hver klasse eller modul kan man få tak i uten noe ekstra eller ved å bruke skop-operatoren ``::'' uten noe foran. Konstanter kan ikke defineres i metoder.

OUTER_CONST = 99
class Const
  def getConst
    CONST
  end
  CONST = OUTER_CONST + 1
end
Const.new.getConst » 100
Const::CONST » 100
::OUTER_CONST » 99

Globale variabler er tilgjengelig overalt i et program. Hver referanse til en gitt globalt navn returnerer samme objekt. Referanser til globale variabler som ikke har blitt initialisert returnerer nil.

Klassevariabler er tilgjengelig alle steder i kroppen til en klasse eller modul. Klassevariabler må initialiseres før bruk. En klassevariabel er delt mellom alle instanser av klassen og er tilgjenglig inne i selve klassen.

class Song
  @@count = 0
  def initialize
    @@count += 1
  end
  def Song.getCount
    @@count
  end
end

Klassevariabler tilhører den innerste omliggende klassen eller modulen. Klassevariabler brukt på toppnivået defineres inne i Object og oppfører seg som globale variabler. Klassevariabler definert i singleton-metoder tilhører mottakeren dersom det er en klasse eller modul; hvis ikke tilhører de klassen til mottakeren.

class Holder
  @@var = 99
  def Holder.var=(val)
    @@var = val
  end
end

a = Holder.new def a.var   @@var end

Holder.var = 123
a.var » 123

Instansvariabler er tilgjengelig i instansmetoder hele veien gjennom en klassekropp. Referering til en instansvariabel som ikke er initialisert returnerer nil. Hver instans av en klasse har et unikt sett av instansvariabler. Instansvariabler er ikke tilgjengelig i klassemetoder.

Lokale variabler er unike i at deres skop er bestemt statisk, mens deres eksistens bestemmes dynamisk.

En lokal variabel lages dynamisk når den for første gang blir tilordnet en verdi under utføring av programmet. Skopet til en lokal variabel er derimot statisk bestemt til å være: den umiddelbart omliggende blokken, metodedefinisjonen, klassedefinisjonen, moduldefinisjonen eller toppnivå programmet. Forsøk på å referere til en lokal variabel som er i skopet men ikke ennå har blitt laget genererer et NameError-unntak.

Lokale variabler med samme navn utgjør forskjellige variabler dersom de dukker opp i atskilte skop.

Metodeparametre anses som lokale variabler til metoden.

Blokkparametre blir tilordnet verdier når blokken kalles.

a = [ 1, 2, 3 ]
a.each { |i|  puts i  }  # i local to block
a.each { |$i| puts $i }  # assigns to global $i
a.each { |@i| puts @i }  # assigns to instance variable @i
a.each { |I|  puts I  }  # generates warning assigning to constant
a.each { |b.meth| }      # invokes meth= in object b
sum = 0
var = nil
a.each { |var| sum += var }  # uses sum and var from enclosing scope

Dersom en lokal variabel (eller blokkparameter) først tilordnes en verdi i en blokk, er den lokal til blokken. Hvis det derimot eksisterer en variabel med samme navn når blokket utføres, vil blokken arve den variabelen.

En blokk tar til seg det settet av lokale variabler som eksisterer på det tidspunkt den opprettes. Dette utgjør del av dens binding. Merk at selv om bindingen til variablene er fastlåst på dette tidspunkt vil blokken ha tilgang til de nåværende verdiene til disse variablene når den utføres. Bindingen opprettholder disse variablene selv om det opprinnelige omsluttende skopet blir slettet.

Kroppene til while, until og for-løkker utgjør en del av skopet som inneholder dem. Lokale variabler opprettet før løkken kan bli brukt inni løkken og eventuelle nye lokale variabler vil være tilgjengelig på utsiden av løkken etterpå.

Forhåndsdefinerte variabler

Følgende variabler er definert på forhånt i Ruby-fortolkeren. I disse beskrivelsene vil notasjonen [r/o] indikere at variablene kun kan leses (read-only). Tross alt, du vil neppe ønske å endre betydningen av true halveis inne i programmet ditt (med mindre du er en politiker). Elementer markert med [thread] er trådlokale.

Mange globale variabler ser ut som banning fra Snoopy: $_, $!, $& og så videre. Dette skyldes ``historiske'' grunner, siden mesteparten av disse variabelnavnene kommer fra Perl. Dersom du syntes det er vanskelig å huske på hva alle disse symbolene betyr, ta en titt på biblioteksfilen kalt ``English'' som er dokumentert på side 445. Den gir mer beskrivende navn til de mest brukte globale variablene.

I de tabellene over variabler og konstanter som følger under, viser vi variabelens navn, typen til objektet som referes og en beskrivelse.

Unntaksinformasjon

$! Exception Unntaksobjektet som ble sent til raise. [thread]

$@ Array Kallstakken til det siste unntaket. Se Kernel#caller på side 413 for detaljer. [thread]

Variabler for mønstergjenkjenning

Alle disse variablene (med unntak av $=) settes til nil etter at en mønstersammenligning har feilet.

$& String Strengen som slo ut på siste suksessfylte mønstersammenligning. Variabelen er lokal i det gjeldende skopet. [r/o,thread]

$+ String Innholdet til den høyeste grupperingen i det siste suksessfylte mønstersammenligning. Det vil si, i "cat" =~/(c|a)(t|z)/ vil $+ bli satt til ``t''. Variabelen er lokal i det gjeldende skopet. [r/o,thread]

$` String Strengen forut for biten som slo ut i siste mønstergjenkjenning. Variabelen er lokal i det gjeldende skopet. [r/o,thread]

$' String Strengen som følger etter biten som slo ut i siste mønstergjenkjenning. Variabelen er lokal i det gjeldende skopet. [r/o,thread]

$= Object Dersom denne variablen er satt til noe annet enn nil eller false vil det ikke gjøres forskjell på store og små bokstaver i alle mønstergjenkjenninger, strengsammenligninger og i hashverdier til strenger.

$1 til $9 String Innholdet til de påfølgende gruppene i siste mønstergjenkjenning. For eksempel vil "cat" =~/(c|a)(t|z)/ sette $1 til ``a'' og $2 til ``t''. Denne variabelen er lokal i det gjeldende skopet. [r/o,thread]
$~ MatchData Et objekt som innekapsler resultatene fra en suksessfylt mønstergjenkjenning. Variablene $&, $`, $' og $1 til $9 blir alle utledet fra $~. Tilordning til $~ endrer de utledete variablene. Variabelen er lokal i det gjeldende skopet. [thread]

Innput/Utput variabler

$/ String Avgrensningstegn i innput (vanligvis linjeskift). Dette er verdien rutiner som Kernel#gets bruker for å bestemme grensene til deler av innput. Dersom den er satt til nil vil gets lese hele filen.

$-0 String Synonym for $/.

$\ String Strengen som legges til på slutten av utput ved hvert kall til metoder slik som Kernel#print og IO#write . Default verdi er nil.

$, String Avgrensningsstrengen som sendes i mellom parametrene til metoder slik som Kernel#print og Array#join . Default verdi er nil, som ikke legger til noe tekst.

$. Fixnum Nummeret til den siste linjen som ble lest inn fra den gjeldende innputfilen.

$; String Det avgrensningsmønsteret som vanligvis brukes av String#split . Kan settes fra kommandolinjen ved å bruke opsjonen -F.

$< Object Et objekt som gir tilgang til innholdet av alle filene som ble gitt som kommandolinje argumenter, eller $stdin (dersom det ikke ble gitt noen argumenter). $< støtter metoder omtrent som et File-objekt: binmode, close, closed?, each, each_byte, each_line, eof, eof?, file, filename, fileno, getc, gets, lineno, lineno=, pos, pos=, read, readchar, readline, readlines, rewind, seek, skip, tell, to_a, to_i, to_io, to_s, i tillegg til metodene i Enumerable. Metoden file returnerer et File-objekt for filen som leses akkurat nå. Dette kan endre seg etter hvert som $< leser seg gjennom filene på kommandolinjen. []

$> IO Målet for utput sendt ved hjelp av Kernel#print og Kernel#printf . Standardverdien er $stdout.

$_ String Den siste linjen som ble lest inn med Kernel#gets eller Kernel#readline . Mange strengrelaterte funksjoner i Kernel-modulen opererer på $_ per default. Variabelen er lokal i det gjeldende skopet. [thread]

$defout IO Synonym for $>.

$-F String Synonym for $;.

$stderr IO Det gjeldende målet for standard feilutput.

$stdin IO Den gjeldende kilden til standard innput.

$stdout IO Det gjeldende målet for standard utput.

Miljøvariabler

$0 String Navnet på toppnivå Ruby programmet som blir kjørt. Typisk vil dette være programmets filnavn. På noen operativsystemer kan man tilordne denne variabelen for å endre navnet som prosessen rapporterer når man (f.eks.) kaller ps(1) kommandoen.

$* Array En Array av strenger som inneholder kommandolinjeopsjonene fra når programmet ble kjørt. Opsjonene som Ruby-fortolkeren bruker vil ha blitt fjernet. [r/o]

$" Array En Array som inneholder filnavnene til modulene som har blitt lastet inn ved bruk av require. [r/o]

$$ Fixnum Prosessnummeret til programmet som kjører. [r/o]

$? Fixnum Avslutningskoden til den siste barneprosessen som terminerte. [r/o, thread]

$: Array En Array av strenger hvor hver string spesifiserer en katalog hvor Ruby skal søke etter skript og binære utvidelser som hentes inn ved hjelp av metodene load og require. Den opprinnelige verdien utgjøres av argumentene sendt inn via kommandolinjeopsjonen -I, fulgt av en installasjonsavhengig standard biblioteksplassering og til sist den gjeldende katalogen (``.''). Denne variabelen kan endres fra inne i et program for å endre søkestien. Typisk brukes $: << dir til å legge til katalogen dir til søkestien. [r/o]

$-a Object Er lik true dersom kommandolinjeopsjonen -a ble gitt. [r/o]

$-d Object Synonym for $DEBUG.

$DEBUG Object Satt til true dersom kommandolinjeopsjonen -d ble angitt.

__FILE__ String Navnet til den nåværende kildekodefilen. [r/o]

$F Array En Array som mottar den oppdelte innputlinjen dersom kommandolinjeopsjonen -a brukes.

$FILENAME String Navnet til den gjeldende innputfilen. Ekvivalent med $<.filename. [r/o]

$-i String Dersom overskrivende editeringsmodus (in-place edit mode) er slått på (f.eks. ved bruk av kommandolinjeopsjonen -i) vil $-i inneholde filendelsen som brukes når backupfilen lages. Dersom du setter en verdi inn i $-i slår du på overskrivende editeringsmodus. Se side 136.

$-I Array Synonym for $:. [r/o]

$-K String Spesifiserer det multibyte kodingssystemet (eller tegnsettet) som skal brukes for strenger og regulære uttrykk. Ekvivalent med kommandolinjeopsjonen -K. side 137.

$-l Object Satt til true dersom kommandolinjeopsjonen -l (som slår på prosessering av linjeendelser) ble angitt. Se side 137. [r/o]

__LINE__ String Nummeret til den gjeldende linjen i kildekodefilen. [r/o]

$LOAD_PATH Array Et synonym for $:. []

$-p Object Satt til true dersom kommandolinjeopsjonen -p (som legger en implisitt while gets ... end løkke rundt programmet ditt) er angitt. Se side 137. []

$SAFE Fixnum Det gjeldende sikkerhetsnivået (se side 254). Variabelens verdi kan ikke reduseres ved tilordning. [thread]

$VERBOSE Object Satt til true dersom en av kommandolinjeopsjonene -v, --version eller -w ble angitt. Ved å sette denne opsjonen til true vil fortolkeren og noen biblioteksrutiner rapportere ytterlige informasjon.

$-v Object Synonym for $VERBOSE.

$-w Object Synonym for $VERBOSE.

Standard Objects

ARGF Object Et synonym for $<.

ARGV Array Et synonym for $*.

ENV Object Et Hash-lignende objekt som inneholder programmets miljøvariabler. ENV er en instans av klassen Object som implementerer hele settet av Hash sine metoder. Brukes for å hente og sette verdiene til miljøvariabler, slik som ENV["PATH"] og ENV['term']="ansi".

false FalseClass Eneste (singleton) instans av klassen FalseClass. [r/o]

nil NilClass Eneste (singleton) instans av klassen NilClass. Default verdi til instans- og globale variabler som ikke har blitt initialisert. [r/o]

self Object Mottakeren (objektet) til den gjeldende metoden. [r/o]

true TrueClass Eneste (singleton) instans av klassen TrueClass. [r/o]

Globale konstanter

De følgende konstantene blir definert av Ruby-fortolkeren.

DATA IO Dersom hovedprogramfilen inneholder direktivet __END__ så vil konstanten DATA settes opp slik at ved å lese fra den får man tak i linjene som følger etter __END__ i kildekodefilen.

FALSE FalseClass Synonym for false.

NIL NilClass Synonym for nil.

RUBY_PLATFORM String Identifiserer plattformen programmet kjører på. Denne strengen er bygd opp på samme måte som plattformidentifikatoren som GNU configure verktøyet bruker (og det er ikke tilfeldig).

RUBY_RELEASE_DATE String Datoen denne versjonen av Ruby ble sluppet.

RUBY_VERSION String Versjonsnummeret til fortolkeren.

STDERR IO Den faktiske standard feilstrømmen til programmet. Er også den opprinnelige verdien til $stderr.

STDIN IO Den faktiske standard innputstrømmen til programmet. Er også den opprinnelige verdien til $stdin.

STDOUT IO Den faktiske standard utputstrømmen til programmet. Er også den opprinnelige verdien til $stdout.

TOPLEVEL_BINDING Binding Et Binding-objekt som representerer konteksten til Ruby sitt toppnivå---hvor programmer blir startet.

TRUE TrueClass Synonym for true.

Uttrykk

Enkelttermer

Enkelttermer i et uttrykk kan utgjøres av en av de følgende.

Operatoruttrykk

Ruby operatorer (fra høy til lav presedens)
Metode Operator Beskrivelse
Ja [ ]  [ ]= Elementreferanse, sett av elementer
Ja ** Opphøying i potens (Exponentiation).
Ja !  ~  +  -- Negetering (not), komplement, unær pluss og unær minus (metodenavn for de to siste er +@ and -@)
Ja *  /  % Multiplisere, dividere og rest-operator (modulo).
Ja +  -- Pluss og minus
Ja >>  << Bitshift mot høyre og venstre
Ja & Bitvis `og'-operator
Ja ^  | Bitvis ekslusiv `eller' (XOR) og vanlig `eller' (OR)
Ja <=  <  >  >= Sammenligningsoperatorer
Ja <=>  ==  ===  !=  =~  !~ Operatorer for likhets- og mønstersammenligning (!= og !~ kan ikke defineres som metoder)
&& Logisk `og'
|| Logisk `eller'
..  ... Rekke (inklusiv og eksklusiv)
? : Ternær if-then-else konstruksjon
=  %=  { /=  --=  +=  |= &=  >>=  <<=  *=  &&=  ||=  **= Tilordning
defined? Sjekk om symbol er definert
not Logisk negetering
or  and Logisk komposisjon
if  unless  while  until Uttrykksmodifikatorer
begin/end Blokkuttrykk

Uttrykk kan kombineres ved hjelp av operatorer. Tabell 18.4 på side 219 lister Ruby sine operatorer ordnet etter presedens. Operatorene med "Ja" i metode kolonnen er implementert som metoder og kan derfor overstyres.

Mer om tilordning

Tilordningsoperatoren setter en eller flere r-verdier inn i en eller flere l-verdier. Hva som menes med tilordning vil være avhengig av hver individuell l-verdi.

Dersom en l-verdi er et navn på en variabel eller konstant, vil den variabelen eller konstanten motta en referanse til den korresponderende r-verdien.

a, b, c = 1, "cat", [ 3, 4, 5 ]

Dersom l-verdien er en objektattributt vil den korresponderende metoden for å sette attributtet bli kalt på mottakeren, med r-verdien som parameter.

anObj = A.new
anObj.value = "hello"   # ekvivalent med anObj.value=("hello")

Dersom l-verdien er en referanse til et tabellelement, kaller Ruby elementtilordningsoperatoren (``[]='') på mottakeren. Som parametre sendes eventuelle indekser som dukker opp mellom firkantparantesene, fulgt av r-verdien. Dette illustreres i tabell 18.5 på side 220.

Kobling mellom elementreferanse og metodekall

Elementreferanse Faktisk metodekall
anObj[] = "one" anObj.[]=("one")
anObj[1] = "two" anObj.[]=(1, "two")
anObj["a", /^cat/] = "three" anObj.[]=("a", /^cat/, "three")

Parallell tilordning

Et tilordningsuttrykk kan ha en eller flere l-verdier og en eller flere r-verdier. Dette avsnittet forklarer hvordan Ruby behandler tilordninger med forskjellige kominasjoner av argumenter.

  1. Dersom siste r-verdi har en stjerne (*) foran seg og er en Array så blir r-verdien byttet ut med elementene i tabellen, med hvert element som en egen r-verdi.
  2. Dersom tilordningen inneholder flere l-verdier og en r-verdi, så blir r-verdien gjort om til en Array som ekspanderes til et sett av r-verdier slik som i (1).
  3. Påfølgende r-verdier tilordnes til l-verdiene. Denne tilordningen skjer i effekt i parallell, slik at (for eksempel) a,b=b,a bytter om på verdiene i ``a'' og ``b.''
  4. Dersom det er flere l-verdier enn r-verdier vil de øvrige bli tilordnet med nil.
  5. Dersom det er flere r-verdier enn l-verdier vil de overflødige r-verdiene ignoreres.
  6. Disse reglene endrer seg litt dersom siste l-verdi har en stjerne (*) foran seg. Denne l-verdien vil alltid motta en Array under tilordning. Tabellen vil inneholde den r-verdien som normalt ville blitt tilordnet til denne l-verdien, og eventuelt de øvrige ekstra r-verdiene.
  7. Dersom en l-verdi er en liste omgitt av parenteser vil den bli behandles tom et nøstet tilordningsuttrykk, og listen tilordnes ut fra de korresponderende r-verdiene som beskrevet i disse reglene.

Innføringen har eksempler som starter på side 75

Blokkuttrykk

begin
  kropp
end

Uttrykk kan grupperes mellom begin og end. Verdien til blokkuttrykket er verdien til det siste uttrykket som evalueres.

Blokkuttrykk kan også spille en rolle i forbindelse med håndtering av unntak, som diskuteres på side 235.

Boolske uttrykk

Boolske uttrykk evaluerer til en sannhetsverdi. Noen konstruksjoner i Ruby (spesielt rekker) oppfører seg anderledes når de evalueres i et boolsk uttrykk.

Sannhetsverdier

Ruby forhåndsdefinerer de globale verdiene false og nil. Begge disse verdiene behandles som usanne i en boolsk sammenheng. Alle andre verdier behandles som sanne.

And, or, not og defined?

Operatorene and og && evaluerer den første operanden. Dersom den er usann returnerer uttrykket usann, hvis ikke returnerer uttrykket verdien til den andre operanden.

expr1  and  expr2
expr1  &&   expr2

Operatorene or og || evaluerer den første operanden. Dersom den er sann returnerer uttrykket sann, hvis ikke returnerer uttrykket verdien til den andre operanden.

expr1  or  expr2
expr1  ||  expr2

Operatorene not og ! evaluerer operanden. Dersom den er sann, returnerer uttrykket usann og motsatt.

Varianten av disse operandene skrevet med hele ord (and, or og not) har lavere presedens enn de tilsvarende formene som kun utgjøres av symboler (&&, || og !). Se tabell 18.4 på side 218.

Operatoren defined? returnerer nil dersom argumentet dens, som kan være et vilkårlig uttrykk, ikke er definert. Hvis ikke returneres en beskrivelse av argumentet. For noen eksempler, se side 78 i innføringen.

Sammenligningsoperatorer

Ruby sin syntaks definerer sammenligningsoperatorene ==, ===, <=>, <, <=, >, >=, =~ samt standardmetodene eql? og equal? (se tabell 7.1 på side 79). Alle disse operatorene er implementert som metoder. Selv om operatorene har en intuitiv betydning, er det opp til klassene som implementerer dem å innføre en meningsfull semantikk. Biblioteksreferansen som starter på side 275 beskriver sammenligningssemantikken til de innebygde klassene. Modulen Comparable tilbyr støtte slik at man kan implmentere operatorene ==, <, <=, >, >= og metoden between? ut fra <=>. Operatoren === brukes i case-uttrykk, som beskrevet på side 223.

Både == og =~ har negeterte former, != og !~. Ruby konverterer mellom disse når syntaksen analyseres: a!=b blir til !(a==b) og a!~b blir til !(a =~b). T Det er ingen metoder som tilsvarer != eller !~.

Rekker i boolske uttrykk

if  expr1 .. expr2
while  expr1 ... expr2

Når en rekke blir brukt i et boolsk uttrykk fungerer den som en "flipp-flipp". Den har to tilstander, satt og usatt, og er til å begynne med usatt. På hvert kall går rekken igjennom tilstandsmaskinen vis i figur 18.1 på 223. Rekken returnerer true dersom den er i satt-tilstand ved slutten av kallet og false ellers.

Rekker av to-punktumsformen oppfører seg litt anderledes enn rekker av tre-punktumsformen. Når en to-punktums rekke først går i fra usatt til satt, evaluerer den umiddelbart avslutningsbetingelsen for så å gjøre den overgangen. Dette betyr at dersom både expr1 og expr2 evaluerer til true på et og samme kall, så vil to-punktumsformen avslutte kallet i usatt tilstand. Men den returnerer dog true for selve kallet.

Forskjellen kan illustreres av følgende kode:

a = (11..20).collect {|i| (i%4 == 0)..(i%3 == 0) ? i : nil}
a » [nil, 12, nil, nil, nil, 16, 17, 18, nil, 20]
a = (11..20).collect {|i| (i%4 == 0)...(i%3 == 0) ? i : nil}
a » [nil, 12, 13, 14, 15, 16, 17, 18, nil, 20]

Figur 18.1: Tilstandsoverganger for boolske rekker

Regulære uttrykk i boolske uttrykk

Dersom et regulært uttrykk alene forekommer som et boolsk uttrykk, sammenlignes det med den gjeldende verdien til variabelen $_.

if /re/ ...
tilsvarer

if $_ =~ /re/ ...

If- og unless-uttrykk

if boolsk-uttrykk [then]
  body
elsif boolsk-uttrykk [then]
  body
else
  body
end

unless boolsk-uttrykk [then]
  body
else
  body
end

Nøkkelordet then separerer kroppen fra betingelsen. Det er ikke påkrevd dersom kroppen starter på en ny linje. Verdien til et if- eller unless-uttrykk er det samme som verdien til siste uttrykket som evalueres når kroppen utføres.

If- og unless-modifikatorer

uttrykk if     boolsk-uttrykk
uttrykk unless boolsk-uttrykk

evaluerer uttrykk kun dersom boolsk-uttrykk er true (false for unless).

Ternæroperator

boolsk-uttrykk ? expr1 : expr2

returnerer expr1 dersom boolsk-uttrykk er sann og expr2 hvis ikke.

Case-uttrykk

case target
  when [
            comparison
            ]+
             [then]
    body
  when [
            comparison
            ]+
             [then]
    body
  ...
[ else
    body ]
 end

Et case-uttrykk starter øverst til venstre og utfører sammenligningene comparison === target. Når en sammenligning returnerer sann stoppes søket og kroppen assosiert med den sammenligningen utføres. Deretter returnerer case verdien til det siste evaluerte uttrykket i kroppen. I de tilfeller hvor ingen comparison slår til, vil en eventuelt else-klausul bli utført. Dersom det heller ikke er en else tilstede, vil case stille og forsiktig returnere nil.

Nøkkelordet then separerer when-sammenligningene fra kroppene og er ikke nødvendig dersom kroppen starter på en ny linje.

Løkker

while boolsk-uttrykk [do]
  body
end

utfører body null eller flere ganger så lenge boolsk-uttrykk er sant.

until boolsk-uttrykk [do]
  body
end

utfører body null eller flere ganger så lenge boolsk-uttrykk er usant.

I begge variantene separerer do boolsk-uttrykk fra body og kan utelates dersom kroppen starter på en ny linje.

for [
            name
            ]+
             in uttrykk [do]
  body
end

Løkken for utføres som om den var følgende each-løkke, med det unntak at lokale variabler derfinert i kroppen til for-løkken vil være tilgjengelig på utsiden av løkken, mens de definert inne i en iteratorblokk ikke vil være det.

uttrykk.each do | [
            name
            ]+
             |
  body
end

loop, som itererer over dens assosierte blokk, er ikke en konstruksjon i språket---det er en metode i modulen Kernel.

While- og until-modifikatorer

uttrykk while boolsk-uttrykk
uttrykk until boolsk-uttrykk

Dersom uttrykk er noe annet enn en begin/end-blokk utføres uttrykk null eller flere ganger så lenge boolsk-uttrykk er true (false for until).

Dersom uttrykk er en begin/end-blokk vil blokken alltid bli utført minst en gang.

Break, redo, next og retry

break, redo, next og retry forandrer den normale flyten gjennom en while, until, for eller iterator-kontrollert løkke.

break terminerer den umiddelbart omsluttende løkken---kontrollen fortsetter fra uttrykket som følger etter blokken. redo gjentar løkken fra starten av, men uten å hverken reevaluere betingelsen eller hente neste element (i en iterator). next hopper til slutten av løkken slik at neste iterasjon starter. retry starter løkken på nytt og reevaluerer betingelsen.

Metodedefinisjon

  def defname [( [
            arg [=val
            ]
            ]*
             [, *vararg
            ] [, &blockarg
            ] )]
    body
  end

defname er både navnet på metoden og eventuelt konteksten den er gyldig i.

defname <- methodname
expr.methodname

Et metodenavn er enten en operator som kan redefineres (se tabell 18.4 på side 219) eller et navn. Dersom metodenavn er et navn, bør den starte med en liten bokstav (eller understrekningstegn) og eventuelt følges av store og små bokstaver, understrekningstegn og sifre. Et metodenavn kan også ha et spørsmålstegn (``?''), utropstegn (``!'') eller likhetstegn (``='') på slutten. Spørsmåls- og utropstegnet er bare en del av navnet til metoden. Likhetstegnet er også en del av navnet med gir også beskjed om at denne metoden er en attributtsetter (beskrevet på side 23).

En metodedefinisjon med et navn uten videre utsmykking i en klasse- eller moduldefinisjon lager en instansmetode. En instansmetode kan bare kalles ved å sende dens navn til en mottaker som er en instans av klassen den er definert i (eller en av den klassens subklasser).

Utenfor en klasse- eller moduldefinisjon vil en definisjon med et ikke utsmykket metodenavn blir lagt til som en privat metode i klassen Object og kan derfor kalles i enhver kontekst uten en eksplisitt mottaker.

En definisjon som bruker et metodenavn på formen expr.methodname lager en metode assosiert med objektet som er verdien til uttrykket; metoden kan kun kalles ved å bruke det objektet som mottaker. Andre dokumentasjonskilder kaller disse metodene for singleton metoder.

class MyClass
  def MyClass.method      # definition
  end
end

MyClass.method            # call

anObject = Object.new def anObject.method       # definition end anObject.method           # call

def (1.class).fred        # receiver may be an expression end Fixnum.fred               # call

Metodedefinisjoner kan ikke inneholde klasse-, modul- eller instansmetodedefinisjoner. De kan inneholde nøstede singleton metodedefinisjoner. Kroppen til en metode oppførerer seg som om den var en begin/end-blokk, det vil si at den kan inneholde unntakshåndtering (rescue, else og ensure).

Argumenter til metoder

En metodedefinisjon kan ha null eller flere vanlige argumenter, et valgfritt array-argument og et eventuelt blokk-argument. Argumenter separeres av komma og listen av argumenter kan settes mellom parenteser.

Et vanlig argument er et variabelnavn til en lokal variabel, som eventuelt følges av et likhetstegn og et uttrykk som angir en default verdi. Uttrykket evalueres når metoden kalles. Uttrykkene evalueres i fra venstre mot høyre. Et uttrykk kan referere til en parameter som er før det i argument listen.

def options(a=99, b=a+1)
  [ a, b ]
end
options » [99, 100]
options 1 » [1, 2]
options 2, 4 » [2, 4]

Det valgfrie array-argumentet må komme etter eventuelle vanlige argumenter og kan ikke har en default verdi.

Når metoden kalles vil array-argumentet referere til en ny instans av klassen Array. Dersom metodekallet spesifiserte flere parametre enn antallet vanlige argumenter i definisjonen, vil de øvrige parametrene samles i dette nye Array-objektet.

def varargs(a, *b)
  [ a, b ]
end
varargs 1 » [1, []]
varargs 1, 2 » [1, [2]]
varargs 1, 2, 3 » [1, [2, 3]]

Dersom et array-argument følger etter argumenter med default-verdier vil parametrene først brukes til å overstyre default-verdiene. De gjenværende parametrene vil gå inn i array-argumentet.

def mixed(a, b=99, *c)
  [ a, b, c]
end
mixed 1 » [1, 99, []]
mixed 1, 2 » [1, 2, []]
mixed 1, 2, 3 » [1, 2, [3]]
mixed 1, 2, 3, 4 » [1, 2, [3, 4]]

Det eventuelle blokk-argumentet må være den siste i listen. Når metoden kalles vil Ruby sjekke om det var en blokk assosiert med kallet. Dersom en blokk er tilstede, omdannes det til en instans av klassen Proc og tilordnes blokk-argumentet. Dersom det ikke var noen blokk tilstede, settes blokk-argumentet til nil.

Kalle en metode

  
[ receiver. ] name [ parameters ] [ block ]
[ receiver::] name [ parameters ] [ block ]
parameters <- ( [ param ]* [, hashlist ] [*array ] [&aProc ] )
block <- { blockbody }
do blockbody end

De første parameterene tilordnes de faktiske argumentene til metoden. Disse parameterne kan følges av en liste av nøkkel => verdi par. Disse parene samles i et enkelt Hash-objekt og sendt inn som en enkelt parameter.

Etter disse parametrene kan det følge en enkelt parameter med en asterisk foran. Dersom denne parameteren er et Array-objekt vil Ruby bytte den ut med null eller flere parametre som korresponderer med elementene i Array-objektet.

def regular(a, b, *c)
  # ..
end
regular 1, 2, 3, 4
regular(1, 2, 3, 4)
regular(1, *[2, 3, 4])

En blokk kan assosieres med et metodekall ved å bruke en literal blokk (som må starte på samme kildekodelije som siste line til metodekallet). Eventuelt kan den sendes inn ved en parameter med et og-tegn (&) foran. Parameteren må da holde en referanse til et Proc- eller Method-objekt. Uansett om det er et blokkargument tilstede eller ikke vil Ruby ordne slik at verdien til den globale funksjonen Kernel::block_given? kan gi oss den informasjonen.

aProc   = proc { 99 } anArray = [ 98, 97, 96 ]

def block   yield end block { } block do       end block(&aProc)

def all(a, b, c, *d, &e)   puts "a = #{a}"   puts "b = #{b}"   puts "c = #{c}"   puts "d = #{d}"   puts "block = #{yield(e)}" end

all('test', 1 => 'cat', 2 => 'dog', *anArray, &aProc)
produserer:
a = test
b = {1=>"cat", 2=>"dog"}
c = 98
d = [97, 96]
block = 99

En metode kalles ved å sende dens navn til en mottaker. Dersom ingen mottaker er spesifisert, antas det at man ønsker å bruke self.

Mottakeren ser etter metodedefinisjonen i sin egen klasse og deretter sekvensielt i forelderklassene. Instansmetoder fra inkluderte moduler oppførerer seg som om de var i anonyme superklasser til klassen som inkluderer dem. Dersom metoden ikke blir funnet kaller Ruby metoden method_missing i mottakeren. Standard oppførsel til denne metoden, som er definert i Kernel::method_missing , er å rapportere en feil og terminere programmet.

Når en mottaker blir spesifisert eksplisitt i et metodekall kan den separeres fra metodenavnet enten ved hjelp av punktum ``.'' eller to kolontegn ``::''. Forskjellen mellom disse to variantene slår ut dersom metodenavnet starter med en stor bokstav. I det tilfellet vil Ruby anta at at et metodekall på formen receiver::Thing egentlig er et forsøk på å få tak i en konstant Thing i mottakeren med mindre metodekallet har en parameterliste i mellom parenteser.

Foo.Bar()         #  method call
Foo.Bar           #  method call
Foo::Bar()        #  method call
Foo::Bar          #  constant access

Returnverdien til en metode er verdien til det siste uttrykket som blir evaluert.

return [
            expr
            ]*
            

ET return-uttrykk vil avslutte en metode umiddelbart. Verdien til et return-uttrykk er nil dersom den kalles uten noen parameter, verdien til parameteren dersom det bare gis en parameter eller et Array-objekt dersom det er mer enn en parameter.

super

super  [ ( [
            param
            ]*
             [*array
            ] ) ]  [
            block
            ]

Inne i kroppen til en metode oppfører et kall til super seg akkurat som et kall til den opprinnelige metoden, men søket etter metodedefinisjonen vil starte i superklassen. Dersom ingen parametre (og heller ingen parenteser) sendes til super, vil den opprinnelige metodens parametre bli videresendt; ellers vil angitte parametre til super blir sendt.

Operator-metoder

expr1 operator
operator expr1
expr1 operator expr2

Dersom operatoren i et operator-uttrykk tilsvarer en metode som kan redefineres (se tabell 18.4 på side 219), vil Ruby utføre operator-uttrykket som om den var skrevet slik:

(expr1).operator(expr2)

Tilordne attributter

receiver.attrname = rvalue

Når varianten receiver.attrname dukker opp som en l-verdi kaller Ruby metoden attrname= hos mottakeren og sender rvalue inn som en enkelt parameter.

Elementreferanse-operator

receiver [
              expr
              ]+
               
receiver [
              expr
              ]+
                = rvalue

Når en elementreferanse brukes som en r-verdi, kalles metoden [] på mottakeren og uttrykkene mellom firkantparentesene sendes inn som parametre.

Dersom en elementreferanse brukes som en l-verdi, kalles metoden []= på mottakeren. Parametrene som sendes inn utgjøres av uttrykkene som ble gitt mellom firkantparentesene samt den r-verdien som ønskes tilordnet (rvalue).

Aliasing

alias newName oldName

lager et nytt navn som referer til en eksisterende metode, operator, global variabel eller tilbakereferanse i et regulært uttrykk. ($&, $', $' og $+). Lokale variabler, instansvariabler, klassevariabler og konstanter kan man ikke lage alias til. Parameterne til alias kan være navn eller symboler.

class Fixnum
  alias plus +
end
1.plus(3) » 4
alias $prematch $`
"string" =~ /i/ » 3
$prematch » "str"
alias :cmd :`
cmd "date" » "Sun Nov 25 23:44:19 CST 2001\n"

Når en metode får et alias refererer det nye navnet til en kopi av den opprinnelige metodens kropp. Dersom metoden senere blir redefinert, vil aliaset fremdeles kalle den opprinnelige implementasjonen.

def meth
  "original method"
end
alias original meth
def meth
  "new and improved"
end
meth » "new and improved"
original » "original method"

Klassedefinisjon

class classname  [< superexpr
            ]
  body
end

class << anObject body end

En klassedefinisjon i Ruby lager eller utvider et objekt av klassen Class ved å utføre koden i body. I den første variasjonen blir en navngitt klasse lagd eller utvidet. Det resulterende Class-objektet tilordnes til en global konstant kalt classname. Dette navnet bør begynne med en stor bokstav. I den andre variasjonen blir en anonym (singleton) klasse assosiert med det angitte objektet.

Dersom superexpr er tilstede, bør det være et uttrykk som evaluerer til et Class-objekt som skal installeres som superklassen til klassen som defineres. Hvis det er utelatt, benyttes klassen Object som superklasse.

Inne i body blir de fleste uttrykkene bare utført etter hvert som definisjonen leses. Men:

Det er verdt å peke på at en klassedefinisjon er eksekverbar kode. Mange av direktivene som brukes i en klassedefinisjon (slik som attr og include) er egentlig bare private instansmetoder i klassen Module (dokumentert på side 344).

Kapittel 19, som starter på side 237, beskriver i mer detalj hvordan Class-objekter interagerer med resten av miljøet.

Instansiere objekter av klasser

obj = classexpr.new [ ( [
            args
            ]*
             ) ]

Klassen Class definerer instansmetoden Class#new , som:

Dersom en klassedefinisjon overstyrer klassemetoden new uten å kalle super, så kan ingen objekter av den klassen lages.

Deklarere klasseattributter

Deklarering av klasseattributter er teknisk sett ikke en del av Ruby språket: de er bare metoder definert i klassen Module som automatisk lager metoder for å gi tilgang til attributter.

class name
  attr attribute  [, writable
            ]
  attr_reader     [
            attribute
            ]+
            
  attr_writer     [
            attribute
            ]+
            
  attr_accessor   [
            attribute
            ]+
            
end

Moduldefinisjoner

module name
  body
end

En modul er i bunn og grunn en klasse som ikke kan instansieres. Som for en klasse utføres kroppen når den defineres og det resulterende Module-objektet lagres i en konstant. En modul kan inneholde klasse- og instansmetoder og definere konstanter og klassevariabler. Akkurat som for klasser blir modulmetoder kalt ved å bruke Module-objektet som mottaker og man får tak i konstantene ved å bruke skop-operatoren ``::''.

module Mod
  CONST = 1
  def Mod.method1    # module method
    CONST + 1
  end
end

Mod::CONST » 1
Mod.method1 » 2

Mixin---inkludere moduler

class|module name
  include expr
end

En modul kan inkluderes i definisjonen til en annen modul eller klasse ved å bruke include-metoden. Modul- eller klassedefinisjonen som kaller include får tilgang til konstanter, klassevariabler og instansmetoder til modulen som inkluderes.

Dersom en modul inkluderes i en klassedefinisjon, oppfører det seg som om modulens konstanter, klassevariabler og instansmetoder har blitt samlet i en anonym (og utilgjengelig) superklasse. Blant annet vil instanser av klassen svare på meldinger sendt til modulens instansmetoder.

En modul kan også inkluderes på toppnivå og da blir modulens konstanter, klassevariabler og instansmetoder tilgjengelig på topnivå.

Modulfunksjoner

Selv om include er nyttig for å tilby innblandet (mixin) funksjonalitet, er det også en måte å hente inn konstanter, klassevariabler og instansmetoder fra en modul inn i et annet navnerom. Men funksjonalitet definert i en instansmetode vil ikke være tilgjengelig som en modulmetode.

module Math
  def sin(x)
    #
  end
end

# Eneste måten å få tak i Math.sin er... include Math sin(1)

Metoden Module#module_function løser dette problemet ved å ta en eller flere instansmetoder fra en modul og kopiere deres definisjoner inn i tilsvarende modulmetoder.

module Math
  def sin(x)
    #
  end
  module_function :sin
end

Math.sin(1) include Math sin(1)

Instansmetoden og modulmetoden er to forskjellige metoder: metodedefinisjonen blir kopiert av module_function, det lages ikke et alias.

Tilgangskontroll

Ruby definerer tre beskyttelsesnivåer for modul- og klassemetoder:

private    [
            aSymbol
            ]*
            
protected  [
            aSymbol
            ]*
            
public     [
            aSymbol
            ]*
            

Hver av funksjonene kan brukes på to forskjellige måter.
  1. Uten noen argumenter, setter de tre funksjonene den gjeldende standard tilgangskontroll for de påfølgende metodene som defineres.
  2. Med argumenter, setter funksjonene tilgangskontrollen på de angitte metodene og konstantene.

Tilgangskontrollen sjekkes når en metode kalles.

Blokker, tillukninger (closures) og Proc-objekter

En kodeblokk er et sett med Ruby utsagn og uttrykk mellom krøllparenteser eller et do/end-par. Blokken kan starte med en argumentliste mellom vertikale linjer. En kodeblokk kan kun stå skrevet umiddelbart etter et metodekall. Starten til blokken må være på samme logiske linje som slutten på metodekallet.

invocation  do  | a1, a2, ... |
end

invocation { | a1, a2, ... | }

Krøllparenteser har høyere presedens enn do/end. Dersom metodekallet har paremtre som ikke er samlet innenfor parenteser vil krøllparentes-formen av en blokk bindes til den siste parameteres og ikke det overordnede metodekallet. Formen med do/end vil derimot bindes til metodekallet.

Inne i kroppen til metoden som kalles kan kodeblokken utføres ved å bruke yield. Parameterne som sendes til yield vil bli tilordnet til argumentene i blokken ut fra reglene om parallell tilordning som beskrevet på side 219. Returverdien fra yield-kallet er verdien til det siste uttrykket som ble evaluert i blokken.

En kodeblokk husker på miljøet den var definert i og bruker det miljøet når den kalles.

Proc-objekter

Kodeblokker kan konverteres til instanser av klassen Proc ved å bruke metodene Proc.new og Kernel#proc , eller ved å assosiere blokken med en metodes blokkargument.

Konstruktøren til Proc tar en assosiert blokk og pakker den inn med nok kontekst til å kunne gjenopprette blokkens miljø når den kalles senere. Instansmetoden Proc#call lar deg kalle den opprinnelige blokken og eventuelt angi parametre. Koden i blokken (og den assosierte tillukningen) forblir tilgjengelig så lenge Proc-objektet er i live.

Dersom siste argument i en metodes argumentliste har et og-tegn (``&'') foran, vil en eventuell blokk assosiert med kall til den metoden omformes til et Proc-objekt og tilordnet til den parameteren.

Unntak

Unntak i Ruby er objekter av klassen Exception og dens subklassen (en fullstendig liste av de innebygde unntakene finnes i figur 8.1 på side 91).

Heve unntak

Metoden Kernel::raise hever et unntak.

raise
raise aString
raise thing [, aString [
            aStackTrace
            ]
            ]

Den første varianten hever unntaket i $! på nytt eller et nytt RuntimeError-unntak dersom $! er nil. Den andre varianten lager et nytt RuntimeError-unntak med den angitte strengen som feilmelding. Den tredje varianten lager et unntaksobjekt ved å kalle metoden exception på det første argumentet. Deretter settes dette unntakets melding og stakkhistorikk lik henholdsvis andre og tredje argument. Klassen Exception og objekter av klassen Exception inneholder factory-metoder kalt exception, slik at klassenavnet til, eller en instans av, et unntak kan brukes som første parameter til raise.

Når et unntak heves, plasserer Ruby en referanse til Exception-objeket i den globale variabelen $!.

Håndtere unntak

Blokker kan håndteres i skopet til en begin/end-blokk.

  begin
    code...
    code...
[rescue  [parm]*
             [=> var
            ] [then]
    error handling code... ]*
            
[else
    no exception code...
            ]
[ensure
    always executed code...
            ]
  end

En slik blokk kan ha flere rescue-klausuler, og hver rescue kan spesifisere null eller flere parametre. En rescue-klausul uten parametre behandles som om den hadde en parameter; StandardError.

Når et unntak heves, søker Ruby opp gjennom kallstakken til den finner en omsluttende begin/end-blokk. Deretter sammenligner Ruby unntaket med rescue-klausulenes parameter i tur og orden. Hver parameter sammenlignes med $!.kind_of?(parameter). Dersom det hevede unntaket passer med en rescue-klausul så utfører Ruby kroppen til den klausulen og stopper søket. Dersom en rescue-klausul som slår til slutter med => og et variabelnavn, så settes den variabelen til $!.

Selv om parameterne til rescue-klausuler vanligvis er navn på Exception-klasser, så kan de være vilkårlige uttrykk (også metodekall) som returnerer en passende klasse.

Dersom ingen av rescue-klausulene passer med det hevede unntaket, beveger Ruby seg oppover i kallstakken på utkikk etter en begin/end-glokk på et høyere nivå med en klausul som passer. Dersom ett unntak kommer seg helt opp på toppnivå uten å bli fanget opp av en rescue vil programmet termineres med en feilmelding.

Dersom det er en else-klausul tilstede vil kroppen dens bli utført dersom ingen unntak ble hevet i den opprinnelige koden. Unntakt som heves i else-klausulen blir ikke fanget av rescue-klasulene som er i samme blokk som else-klausulen.

Hvis det er en ensure-klausul vil dens kropp alltid bli utført når blokken avsluttes (selv om et uhåndtert unntak er i ferd med å bli sendt oppover kallstakken).

Prøve en blokk på nytt

Ved å bruke retry i fra en rescue-klausul kan man starte den omliggende begin/end-blokken på nytt fra begynnelsen.

Catch og throw

Metoden Kernel::catch utfører den assosierte blokken.

catch ( aSymbol | aString )  do
  block...
end

Metoden Kernel::throw avbryter den normale prosesseringen.

throw( aSymbol | aString [, anObject
            ] )

Når en throw utføres, søker Ruby oppover i kallstakken etter den første catch-blokken med et symbol eller en streng som passer. Dersom Ruby finner en passende catch-blokk, stopper søket og utførselen fortsetter etter slutten på catch-blokken. Hvis det var gitt en parameter til throw-kallet, vil den brukes som returverdi fra catch-blokken. Ruby vil oppfylle eventuelle ensure-klausuler i blokkuttrykken som den traverserer i søken etter en korresponderende catch.

Dersom ingen catch-blokk passer til throw-utsagne, vil Ruby heve et NameError-unntak med utgangspunkt der throw ble kalt.

( In progress translation to Norwegian by NorwayRUG. $Revision: 1.18 $ )
$Log: language.xml,v $
Revision 1.18  2003/08/04 16:07:10  kent
Første utkast til language.


Forrige < Innhold ^
Neste >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide".
Translation to norwegian by Norway Ruby User Group.
Copyright for the english original authored by David Thomas and Andrew Hunt:
Copyright © 2001 Addison Wesley Longman, Inc.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).

(Please note that the license for the original has changed from the above. The above is the license of the original version that was used as a foundation for the translation efforts.)

Copyright for the norwegian translation:
Copyright © 2002 Norway Ruby User Group.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at
http://www.opencontent.org/openpub/).
Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.
Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.