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
Personliga verktyg