Ruby
Från Unix.se, den fria unixresursen.
Ruby är ett skriptspråk skrivet av Yukihiro Matsumoto (alias Matz). Det har influerats av flera andra programspråk, främst Perl men också Java, Eiffel, Smalltalk, Lisp och Python. En av de största skillnaderna mellan Perl och Ruby är att Ruby har betydligt bättre stöd för objektorienterad programmering. Det var till skillnad från Perl redan från början tänkt att vara objektorienterat, och det går faktiskt längre än både Java och Python i att "allt är ett objekt". Till och med grundläggande typer som heltal är objekt, något som är mycket ovanligt bland programmeringsspråk.
- (För att du ska kunna förstå denna och följande artiklar kommer du förmodligen behöva en viss tidigare erfarenhet av programmering, helst objektorienterad sådan. Men kanske är det inte nödvändigt; en del har lätt för att lära. I vilket fall som helst är detta inte skrivet för de som aldrig programmerat förr.)
Sitt starkaste fäste har Ruby fått i Japan och andra delar i Asien, främst tack vare sitt mycket goda stöd för Unicode. I väst har vi vanligen använt teckenkodningar som är varianter på ASCII med sju eller åtta bitar, som däremed bara kan beskriva 256 olika tecken. Detta räcker gott för våra västerländska alfabet, men flera av de asiatiska skriftspråken har flera tusen olika tecken. Unicode använder sig av ett i princip obegränsat antal bitar och har stöd för i princip alla skriftspråk som finns.
Till skillnad från C++, och i likhet med Java och Smalltalk, låter inte Ruby klasser ärva från mer än en annan klass. Även om multipelt arv (dvs. att ärva från mer än en annan klass) kan vara användbart ibland, leder det oftast bara till onödigt röriga klasshierarkier. Ruby använder istället moduler för att åstadkomma något som liknar multipelt arv, så kallad mix-in, för att ändra beteendet klassers beteende. Liksom i de flesta språk med endast enkelt arv härstammar alla klasser från toppklassen Object (utom Object själv, som är den enda klass som saknar superklass). Moduler kan liksom klasser ha konstanter, funktioner och klasser men kan inte instantieras och kan inte heller ärva. De används alltså endast för att "gruppera" (precis som C++:s namnutrymmen) klasser, funktioner och konstanter.
Liksom Perl stöder Ruby reguljära uttryck i själva språket, och kan användas till mycket som Perl annars används till (analys av loggfiler, konvertering av strängar, osv.).
Innehåll |
Installation av Ruby
Ladda hem Ruby från den officiella hemsidan (http://www.ruby-lang.org/). Installera det på vanligt vis (./configure ; make ; make install) och skriv sedan:
ruby -e 'print "hello, world!"'
Fungerar det så fungerar det, gör det inte det har något gått fel.
Det var en så kallad enradare, något som de som programmerat i Perl förhoppningsvis känner igen. I fortsättningen kommer vi skriva programmen i textfiler. Ruby använder sig av filändelsen ".rb". För att köra ett Ruby-program skriver du exempelvis ruby test.rb
Kommentarer
I Ruby startas kommentarer, precis som i bash, med #. Allt på raden efter # hoppas över av tolken. Som vi strax kommer att se finns det ett undantag från denna regel. Du kan också skriva inbäddad dokumentation med hjälp av =begin och =end (dessa nyckelord får inte föregås av mellanslag eller dylikt).
Exempel:
=begin Det här programmet, om man nu kan kalla det för program, gör absolut ingenting. =end # eftersom allt här är dokumentation eller kommentarer kommer inget hända när det körs av ruby.
Variabler
Som de flesta skriptspråk är Ruby dynamiskt typat, vilket innebär att det automatiskt förstår av vilken typ en variabel är:
s = 'this is a string' # en sträng i = 0 # ett heltal f = 2.7182 # ett flyttal PI = 3.1415927 # en konstant
För att göra en variabel global lägger man till $ i början av dess namn, till exempel $global_variabel. Som vi kommer att se i avsnittet om klasser har @ också en speciell mening i början av variabelnamn. Konstanter skrivs med STORA BOKSTÄVER.
Strängar
Strängar definieras med hjälp av "sträng" eller 'sträng'. Strängar kan repeteras och sammanfogas. Detta görs med * respektive +:
a = 'Strängar ' b = "rulez" c = '!' string = a + b + c*3 print string + "\n" #man kan också skriva ut det direkt: print "#{a + b + c*3}\n" print "string är #{string.length} bokstäver lång\n"
Funktionen length är definierad i klassen String, och returnerar helt enkelt strängens längd.
#{uttryck} används i ""-strängar för att exekvera uttrycket inom { } och sedan konvertera resultatet till en sträng. Resultatet av ett uttryck som bara består av en variabel är alltså variabelns värde.
# startar alltså här INTE en kommentar. I -strängar (som s i exemplet ovan) så ändras inte #{uttryck}, och så kallade escape characters (exempelvis för att få en ny rad) skrivs bara ut som vanligt:
print '#{uttryck}\n '
Man kan också konvertera många saker till strängar genom funktionen to_s, som finns i Object-klassen och därmed finns i alla klasser. Exempel:
i = 610 s = i.to_s print s + " är #{s.length} siffror långt\n"
Härdokument
Så kallade härdokument är användbara när du vill skriva ut text som ska se ut som den gör i källkoden. De startas med << följt av slutordet, och det får inte finnas någonting efter slutordet när det avslutar härdokumentet. Exempel:
foobar = "ja" print <<sluta_nu det här är ett litet exempel på ett härdokument. i härdokument kan man också skriva ut uttryck som vanligt; värdet av foobar är just nu: #{foobar} #{foobar*2}, dags att sluta... sluta_nu
Heltal och flyttal
Ruby stödjer i princip hur stora tal som helst. Det finns ingen övre gräns för hur stora tal det kan representera, bortsett från hur mycket minne du har i din dator. Eftersom allt är objekt i Ruby kan du använda funktioner på "rena" tal. Här är ett en variant på ovanstående exempel som utnyttjar det:
print "610 är " + 610.to_s.length.to_s + " siffror långt\n"
Värt att lägga märke till är att varken "++tal" eller "tal++" fungerar i Ruby, till skillnad från somliga andra språk. Vill du öka ett tals värde med ett får du skriva "tal += 1".
Matriser
Matriser (eng. arrays) fungerar i Ruby som i de flesta programmeringsspråk. Du bör inte ha några större problem med att förstå det här om du programmerat ett C-liknande språk (eller Perl, för den delen). Exempel:
array = [ "ru", "by", 1, 2, 3, '!' ] print "#{array[0] + array[1] + array[5]*3}\n "
Eftersom matriser precis som allt annat är objekt kan du använda funktioner på dem. Särskilt användbara är push och pop:
array = [ ] # en tom array array.push "ichi" array.push "zwei" array.push "tres" array.push "four" element1 = array.pop element2 = array.pop element3 = array.pop element4 = array.pop print <<here_dokument #{element1} #{element2} #{element3} #{element4} here_dokument
Iteratorer
Liksom i C++ och Java finns det iteratorer i Ruby, men de hämtar sin syntax från Smalltalk och funktionella språk som Haskell, och kan därför se lite udda ut till att börja med. "Learn by example", svårt att förklara på annat vis. Exempel:
array = [ "ru", "by", 1, 2, 3 ] i = 0 array.each { |x| # för varje element, som vi kallar x, gör följande. print "element #{i}: #{x}\n" i += 1 }
{ |x| } är en så kallad omslutning. I ovanstående exempel refererar vi till elementen som "x", men du kan kalla dem för vad du vill - bara byt ut |x| till |vad_du_vill|. De kan användas för andra saker än bara för att gå igenom en lista, men det tar vi inte upp här. Om du är van vid Java kan du se på dem som anonyma funktioner, som fungerar ungefär likadant som anonyma klasser men som har med funktioner att göra. Ruby har även en alternativ syntax för iteratorer:
array = [ "ru", "by", 1, 2, 3 ] i = 0 array.each do |vad_du_vill| print "element #{i}: #{vad_du_vill}\n" i += 1 end
Logiska operatorer
Fungerar precis som i Perl. and eller && för "och", or eller || för "eller", ! för "inte":
a = 1 b = 2 c = 3 print "c är störst\n" if (a < b) and (b < c) print "a är minst\n" if !(c < b and a > c) or (a < b and a < c) print "b är varken störst eller minst\n" if (b < c) and (c > a) and (a != c)
Funktioner
Funktioner definieras med nyckelordet "def", följt av funktionens namn och dess eventuella parametrar. De avslutas med nyckelordet "end". Exempel:
def foo print "hej från foo\n" end print "anropar foo...\n" foo
Naturligtvis kan funktioner även returnera värden. Detta åstadkommes genom att returvärdet av en funktion är detsamma som värdet av det senaste uttrycket (eller explicit, genom att använda nyckelordet return). Exempel:
def foo 1 #värdet av uttrycket "1" är förstås 1 end print "värdet av foo är: #{foo}\n"
För att returnera en sträng gör du likadant -- skriv bara en sträng på en ensam rad så blir den det som funktionen returnerar. Exempel:
def foo "hello from foo" end print "värdet av foo är: #{foo}\n"
Funktioners parametrar definieras genom att rada upp dem efter funktionens namn. Exempel:
def foo a, b, c print "#{a} + #{b} + #{c} = #{a + b + c}\n" end foo 1, 34, 233 # alternativ syntax: foo(3, 377, 34)
Rubys funktioner kan också returnera flera värden (precis som LUA). Då blir det nödvändigt att använda nyckelordet return (om du vill kan du alltid använda return för att returnera värden). Exempel:
def plus_och_minus a, b return a+b, a-b end tal1 = 610 tal2 = 987 summa, differens = plus_och_minus tal1, tal2 print "summan av #{tal1} och #{tal2} är #{summa}, differensen är #{differens}\n"
Reguljära uttryck
Reguljära uttryck är en notation som används för att beskriva vissa mönster i strängar. Exempelvis kan "foo, som inte har bar efter sig, och som får ha hur många tabs och/eller mellanslag framför sig" skrivas som /s*(foo)[^bar]/. Snedstrecken kring uttrycket används för att visa att det är ett RE (reguljärt uttryck). De som är vana vid Unix i allmänhet, eller har programmerat i Perl, känner förmodligen redan till RE:n (bland annat använder grep dem). För att se om en sträng matchar ett RE används =~ (precis som i Perl):
def goodstring string if string =~ /\s*(foo)[^bar]/ "bra sträng" else "mindre bra sträng :)" end end s1 = 'blahblahblah foooooooobar' s2 = ' foo blahaaargh' s3 = 'foobar är inte "bra"' print "'#{s1}' är en #{goodstring s1}\n" print "'#{s2}' är en #{goodstring s2}\n" print "'#{s3}' är en #{goodstring s3}\n"
En lista över Rubys alla speciella tecken i RE:n finns här (http://www.rpi.edu/~carrd/progLang/regex.htm).
Programflödeshantering
Rubys if-else-satser är enkla att förstå och borde inte behöva vidare förklaring. Ett snabbt exempel:
def foo value if value > 10 then "jupp" #returvärdet sätts här alltså till "jupp" elsif value <= 10 then # ett else hade räckt, men vi vill visa elsif "nope" end end integer1 = 89 integer2 = 8 print "integer1 är mer än 10: #{foo integer1}\n" print "integer2 är mer än 10: #{foo integer2}\n"
Du behöver inte använda "elsif" om du inte vill -- det går lika bra med "else if", men eftersom elsif är kortare föredrar en del det (och elsif finns även i många andra programmeringsspråk, så vissa är vana vid det).
Men man kan också använda if på ett annat sätt. Istället för att skriva:
if (a < b) print a end
kan man skriva:
print a if (a < b)
Ruby har även ett nyckelord för if:s motsats: unless. Exempel:
i = 2 unless i == 2 print "i är inte två\n" else print "i är två\n" end #eller... print "i är inte två\n" unless i == 2
Rubys for-satser kan man däremot tycka är lite underliga (såvida man inte är van vid Python). Till skillnad från i C-liknande språk används de bara för att upprepa något ett visst antal gånger, de ändrar inte andra variablars värden. Detta åstadkommes genom att göra ett uppräkningsobjekt med (a..b) som börjar på a och slutar på b (använder två punkter), eller med (a...b) som skapar ett objekt som börjar på a och slutar på b-1 (använder tre punkter).
Syntaxen är enkel och logisk; med for x in (a..b) menas helt enkelt "för varje x i [a,b], gör följande..." Det som i C/C++/Java/etc. skulle skrivas som for(int i = 0; i < 10; i++) kan i Ruby skrivas som for i in (0...10):
def fibo n #returnerar det n:te talet i Fibonacci-serien (kan även åstadkommas med [[rekursion]], men vi vill använda for-loopar här :) if n < 2 return 1 else a = b = 1 for i in (0...n) c = b b = a a = b+c end end return a end for x in (0..9) # skriv ut de 10 första talen i [[Fibonacci]]-serien print "#{fibo x}\n" end
Men det är inte slut på for-loopar än, för i Integer definieras nämligen funktionen "times", som också kan användas som for-loop:
10.times do |i| print "#{i} " end
while fungerar som i de flesta programmeringsspråk:
i = 0 while i < 10 i += 1 print "i är #{i}\n" end
until gör vad man väntar sig:
i = 0 until i == 10 i += 1 print "i är #{i}\n" end
Både while och until kan likt if och unless användas på ett alternativt sätt:
i = 0 i += 1 until i == 10 print "i är nu #{i}\n" i -= 1 while i != 0 print "och nu är i #{i}\n"
case (vilket är i stort sett detsamma som C:s switch, men kraftfullare) kan vara mycket användbart när du behöver testa en variabel mot många olika fall. Istället för att fylla en skärmfull med if-else satser kan case ofta lösa ditt problem betydligt elegantare:
def season month case month when (3..5) "vår" when (5..9) "sommar" when (9..11) "höst" when (1..3) "vinter" when (11..12) "vinter" else "felaktig månad" end end print "När jag fyller år är det #{season 5}\n"
Klasser
Klasser är lite speciella. De definieras ungefär som i C++, Java etc., men för att definiera variabler som är unika för varje instans av en klass används @ i början av variabelns namn. För att definiera variabler som är unika för varje klass men delas av alla instanser av klassen används @@ i början av variabelnamnet. Rubys konstruktor (dvs den funktion som anropas när ett objekt skapas) heter "initialize", och anropas med de argument du ger till new:
class Person def initialize hellomessage @message = hellomessage @firstname = "Okänd" @lastname = "person" end def sayHello print @firstname+" "+@lastname + ": #{@message}\n" end end p = Person.new "heeejsan!" p.sayHello
Nu är det så att Ruby INTE låter andra klasser ändra eller ens läsa en klass' variabler, såvida du inte säger att de ska få göra det. I ovanstående exempel skulle du alltså inte kunna skriva p.firstname = "En". För att kunna göra det måste du skriva en så kallad åtkomstoperator (eng accessor):
#modifikation av ovanstående exempel class Person def initialize @firstname = "Okänd" @lastname = "person" end def firstname #när denna funktion anropas returnerar den värdet på @firstname @firstname end def firstname= value #denna funktion anropas när du skriver person.firstname = "något" @firstname = value end def sayHello print @firstname+" "+@lastname + ": hejsan!\n" end end p = Person.new p.firstname = "En" p.sayHello
Om man behövde skriva åtkomstoperatorer för alla funktioner skulle koden bli ganska rörig. Som tur är kan man istället skriva attr_accessor :firstname för att ge läs- och skrivåtkomst till firstname. Notera att kolonet måste stå framför namnet på variabeln utan något blanktecken emellan. attr_accessor : firstname skulle alltså inte accepteras. attr_accessor ger både läs- och skrivåtkomst, medan attr_reader ger läs- men inte skrivåtkomst, och att_writer ger skriv- men inte läsåtkomst.
För att ärva används "<" följt av namnet på den klass man vill ärva från. Exempel:
class Person def initialize @firstname = "okänd" @lastname = "person" end def sayHello print "#{@firstname} #{@lastname}: hej...\n" end end class Mor < Person # en Mor är en Person attr_reader :lastname # Dotter läser Mor's lastname def initialize firstname, lastname @firstname = firstname @lastname = lastname end end class Dotter < Person # en Dotter är också en Person def initialize firstname, mamma @firstname = firstname @lastname = mamma.lastname end end mor = Mor.new "Elina", "Fernandez" dotter = Dotter.new "Angelica", mor dotter.sayHello
Operatoröverlagring
Detta är en omdiskuterad egenskap hos programmeringsspråk, och det finns argument både för och emot... men Rubys sätt att implementera operatoröverlagring är så logisk att inte ens jag (som är lite emot operatoröverlagring, kanske främst därför att jag är en C-programmerare) kan komma med särskilt många invändningar. Operatorer är i Ruby funktioner som alla andra. När vi sammanfogar strängar med exempelvis:
string = "en" + "sträng " print string
skapas ett strängobjekt för "en", och dess funktion som heter "+" anropas med "sträng" som argument. Resultatet blir, som väntat, "ensträng ". För att göra det lättare att förstå hur det hela går till kommer här exemplet igen, men skrivet på ett annat sätt. Exempel:
string = "en".+("sträng ") print string
Nu när ni vet att operatorer är vanliga funktioner i Ruby, kan ni försöka er på att förstå följande exempel:
class Vektor def initialize values if values.class == Array # values.class returnerar den typ av klass som values är en instans av @element = values.clone else @element = Array.new values, 0 end end def [] num @element[num] end def []= num, value # denna funktion anropas vid uttryck som a[num] = value @element[num] = value end def * annan result = 0 @element.length.times { |i| result += self[i] * annan[i] } result end def + annan @element.length.times { |i| self[i] += annan[i] } self end def - annan @element.length.times { |i| self[i] -= annan[i] } self end def to_s s = "(" @element.each { |e| s += " #{e}" } s + " )" end end a = Vektor.new [ 2, 6, 59 ] a[0] = 3.0 a[1] = 6.0 a[2] = 59.0 print "#{a}\n" b = Vektor.new [ 4, 6, 2 ] print "#{b}\n" print "inre produkten mellan #{a} och #{b} är #{a * b} "
Såg du "felet" med ovanstående kod? Om du inte gjorde det så är det att *-funktionen missbrukas. * kan nämligen betyda flera saker när man använder vektorer: det kan vara inre produkten (även kallad skalärprodukt), det kan vara den yttre produkten (kryssprodukt), och det kan vara en elementvis multiplikation där varje element multipliceras med motsvarande element i den andra vektorn. Det rätta här hade varit att låta bli *-funktionen och istället definiera funktioner såsom "inner" (eller "dot", för dot product som skalärprodukt heter på engelska), "outer" (eller "cross") och "mul".
Låt bli operator-funktionerna om deras funktion kan misstolkas!
Grundläggande in- och utmatning
Vi har redan använt oss en hel del av kommandot "print" för att skriva ut text. Det skriver helt enkelt ut text på skärmen, eller närmare bestämt på den fil som $stdout är satt till (vanligtvis STDOUT). För att läsa från filer (som är objekt) kan filens readline-funktion användas. För att göra ovanstående klass-exempel "interaktivt" (oooh, så spännande det låter) kan vi låta användaren mata in mammans hela namn och sedan dotterns förnamn. Exempel:
class Person @firstname = "okänd" @lastname = "person" def sayHello print "#{@firstname} #{@lastname}: hejsan\n" end end class Mor < Person attr_accessor :lastname def initialize firstname, lastname @firstname = firstname @lastname = lastname end end class Dotter < Person def initialize firstname, mamma @firstname = firstname @lastname = mamma.lastname end end print "Skriv in mammans förnamn: " $stdout.flush #för att texten ska visas även om det inte blivit ny rad måste vi använda flush på stdout firstname = $stdin.readline.chomp # kapa bort med chomp print "Skriv in mammans efternamn: " $stdout.flush lastname = $stdin.readline.chomp mor = Mor.new firstname, lastname print "Skriv in dotterns förnamn: " firstname = $stdin.readline.chomp $stdout.flush dotter = Dotter.new firstname, mor print "Hej från mor\n" mor.sayHello print "Hej från dottern\n" dotter.sayHello