Muutettu viimeksi 23.3.2010 / Sivu luotu 10.3.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
val greetStrings = new Array[String](3) greetStrings(0) = "Hello" greetStrings(1) = ", " greetStrings(2) = "world!\n" for (i <- 0 to 2) print(greetStrings(i))Kiinnostavaa:
val greetStrings: Array[String] = new Array[String](3)Javan tapaan taulukon tyypin määrää alkioiden tyyppi, ei niiden lukumäärä, joten var greetStrings: Array[String]-muuttuja voisi saada arvokseen milloin minkin mittaisia String-taulukoita.
Kaarisulkeiden käyttämiselle taulukoiden indeksoinnissa on perusteensa:
Taulukon voi luoda myös luettelemalla alkiot:
val numNames = Array("zero", "one", "two")Tässäkin kaarisulkeet tarkoittavat apply-metodin kutsua:
val numNames = Array.apply("zero", "one", "two")Array-luokan apply-metodi on itse asiassa ns. factory-metodi, joka luo uuden Array-olion annetulla sisällöllä. Menettely vastaa Javan ja C++:n ns. staattisen luontimetodin käyttöä, ks. vaikkapa Ohjelmoinnin jatkokurssin kohtaa 5.1 Poikkeuksista: Virheiden käsittelyä. Koska Scalassa mikään ei ole static, factory-metodi toteutetaan luokkaan liittyvän singleton-olion ("ainokaisen") metodina. Näin käytettyä "ainokaista" kutsutaan Scala-jargonissa nimellä companion object, "kumppaniolio", "seuralaisolio". Näihin palataan.
Huom: Scalan metodilla voi olla myös vaihteleva määrä parametreja, ns. "toistuvia parametreja". Näihin palataan.
Listat sen sijaan Scalassa ovat muuttumattomia, "immutaabeleita". Java-ohjelmoijalle periaate on tuttu String-tyypistä: kerran luotua String-oliota ei voi kerta kaikkiaan muuttaa millään keinoin. Vaikka tarjolla on suuri joukko operaatioita String-arvojen "muuttamiseen", kaikki "muutetut" arvot ovat todellisuudessa uusia String-olioita. Ja kun viimeinenkin viite johonkin String-olioon poistuu, roskienkerääjä voi halutessaan vapauttaa tuon olion varaaman tilan muuhun käyttöön.
Scalassa on paljon kalustoa, jolla roskienkerääjää voidaan samaan tapaan ylensyöttää. Johdannossa jo tutustuttiin assosiaatiolistaan Map. Roskienkerääjän syöttäminen saattaa aluksi kuulostaa hullulta, mutta pidetäänpä mielessä vaikkapa se, että "immutaabelia" oliota ei tarvitse millään tavoin suojata rinnakkaisohjelmissa. Ja rinnakkaisuutta kohden nykyään mennään... Toisaalta roskienkerääjät alkavat olla aika hyviä nykyään.
Esimerkkejä:
val oneTwoThree = List(1, 2, 3) val oneTwo = List(1, 2) val threeFour = List(3, 4) val oneTwoThreeFour = oneTwo ::: threeFour println(""+ oneTwo +" and "+ threeFour +" were not mutated.") println("Thus, "+ oneTwoThreeFour +" is a new list.")
Niinpä sen sijaan, että kirjoittaisi
val oneTwoThreeFour = oneTwo ::: threeFour,
voi kirjoittaa myös
val oneTwoThreeFour = threeFour.:::(oneTwo).
Operaatio "::" liittää alkion listan alkuun:
val twoThree = List(2, 3) val oneTwoThree = 1 :: twoThree println(oneTwoThree)Tätä operaatiota kutsutaan perinteisesti nimellä "cons", koska se konstruoi uuden listan alkiosta ja listasta.
Tyhjä lista on Nil. Uusi lista voidaan luoda myös lisäämällä tyhjään listaan alkiot yksitellen:
val oneTwoThree = 1 :: 2 :: 3 :: Nil println(oneTwoThree)Tämä ehkä vähän selventää tuota "assosiatiivisuutta oikealta vasemmalle"? Saman voi siis kirjoittaa myös:
val oneTwoThree = Nil.::(3).::(2).::(1) println(oneTwoThree)
Listoille on leegioittain operaatioita, mutta alkion liitämistä listan loppuun ei ole. Perustelut liittyvät tehokkuuteen...
Operaatioita ja ohjelmointitapoja [ks. myös API, ks. List]:
List() or Nil The empty List List("Cool", "tools", "rule") Creates a new List[String] with the three values "Cool", "tools", and "rule" val thrill = "Will" :: "fill" :: "until" :: Nil Creates a new List[String] with the three values "Will", "fill", and "until" List("a", "b") ::: List("c", "d") Concatenates two lists (returns a new List[String] with values "a", "b", "c", and "d") thrill(2) Returns the element at index 2 (zero based) of the thrill list (returns "until") thrill.count(s => s.length == 4) Counts the number of string elements in thrill that have length 4 (returns 2) thrill.drop(2) Returns the thrill list without its first 2 elements (returns List("until")) thrill.dropRight(2) Returns the thrill list without its rightmost 2 elements (returns List("Will")) thrill.exists(s => s == "until") Determines whether a string element exists in thrill that has the value "until" (returns true) thrill.filter(s => s.length == 4) Returns a list of all elements, in order, of the thrill list that have length 4 (returns List("Will", "fill")) thrill.forall(s => s.endsWith("l")) Indicates whether all elements in the thrill list end with the letter "l" (returns true) thrill.foreach(s => print(s)) Executes the print statement on each of the strings in the thrill list (prints "Willfilluntil") thrill.foreach(print) Same as the previous, but more concise (also prints "Willfilluntil") thrill.head Returns the first element in the thrill list (returns "Will") thrill.init Returns a list of all but the last element in the thrill list (returns List("Will", "fill")) thrill.isEmpty Indicates whether the thrill list is empty (returns false) thrill.last Returns the last element in the thrill list (returns "until") thrill.length Returns the number of elements in the thrill list (returns 3) thrill.map(s => s + "y") Returns a list resulting from adding a "y" to each string element in the thrill list (returns List("Willy", "filly", "untily")) thrill.mkString(", ") Makes a string with the elements of the list (returns "Will, fill, until") thrill.remove(s => s.length == 4) Returns a list of all elements, in order, of the thrill list except those that have length 4 (returns List("until")) thrill.reverse Returns a list containing all elements of the thrill list in reverse order (returns List("until", "fill", "Will")) thrill.sort((s, t) => s.charAt(0).toLowerCase < t.charAt(0).toLowerCase) Returns a list containing all elements of the thrill list in alphabetical order of the first character lowercased (returns List("fill", "until", "Will")) thrill.tail Returns the thrill list minus its first element (returns List("fill", "until")) ...
Keskenään eri tyyppisistä arvoista muodostuva järjestetty jono on vallan mainio esimerkiksi tilanteessa, jossa funktion halutaan palauttavan useampia arvoja! Ei tarvitse Javan tapaan määritellä jotakin apuluokkaa arvojen kokoelmaksi.
Tuppelin tyyppejä ovat esim. Tuple2[Int, String], Tuple5[Int, String, Boolean, Double, String], ... Eli tyyppi muodostuu n-tuppelin n:stä ja jonon alkioden tyypeistä.
val pair = (99, "Luftballons") println(pair._1) println(pair._2)
Tekniikka on toteutettu Scalan piirteiden ("trait") peritymishierarkian avulla. Näistä myöhemmin.
Oletusarvoisesti käytössä oleva Set-versio on "immutaabeli":
var jetSet = Set("Boeing", "Airbus") jetSet += "Lear" println(jetSet.contains("Cessna")) val kopioko = jetSet jetSet += "Myrsky" println(kopioko) // Set(Boeing, Airbus, Lear) // EI MUUTOSTA println(jetSet + "Caravelle") // Set(Airbus, Lear, Caravelle, Boeing, Myrsky)
Jos halutaan käyttää muutettavaa versiota Set-luokasta, se on joko tuotava (import) käännösyksikköön tai kutsuttava täydellisellä nimellä, joka sisältää pakkauspolun (scala.collection.mutable.Set):
import scala.collection.mutable.Set val jetSet = Set("Boeing", "Airbus") jetSet += "Lear" println(jetSet.contains("Cessna")) val kopioko = jetSet jetSet += "Myrsky" println(kopioko) // Set(Airbus, Lear, Boeing, Myrsky) println(jetSet + "Caravelle") // Set(Airbus, Lear, Caravelle, Boeing, Myrsky)
import scala.collection.immutable.HashSet val hashSet = HashSet("Tomatoes", "Chilies") println(hashSet + "Coriander")Myös assosiaatiolistasta on joukkoa vastaavalla tavalla toteutettu muutettava ja muuttumaton versio.
Mutaabeli:
import scala.collection.mutable.Map val treasureMap = Map[Int, String]() treasureMap += (1 -> "Go to island.") treasureMap += (2 -> "Find big X on ground.") treasureMap += (3 -> "Dig.") println(treasureMap(2))Ja immutaabeli ilman importia:
val romanNumeral = Map( 1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V" ) println(romanNumeral(4))
import scala.io.Source if (args.length > 0) for (line <- Source.fromFile(args(0)).getLines) print(line.length +" "+ line) else Console.err.println("Please enter filename")Ilmaus Source.fromFile(args(0)) avaa (jos voi) parametrina annetun tiedoston ja palauttaa arvonaan Source-olion. Tämän olion getLines palauttaa iteraattorin Iterator[String], joka antaa tiedoston rivit yksi kerrallaan Stringeinä for-lauseen käyttöön.
Hienompi versio, jossa rivinpituudet kirjoitetaan saman mittaiseen kenttään ja jossa käytetään hienoja(?) välineitä:
import scala.io.Source def widthOfLength(s: String) = s.length.toString.length if (args.length > 0) { val lines = Source.fromFile(args(0)).getLines.toList // Hyi! ;-) val longestLine = lines.reduceLeft( (a, b) => if (a.length > b.length) a else b ) val maxWidth = widthOfLength(longestLine) for (line <- lines) { val numSpaces = maxWidth - widthOfLength(line) val padding = " " * numSpaces print(padding + line.length +" | "+ line) } } else Console.err.println("Please enter filename")
var maxWidth = 0 for (line <- lines) maxWidth = maxWidth.max(widthOfLength(line))Tämäkään ei ole täysin kiinnostamatonta: Int-oliolle on käytettävissä aksessori max, joka palauttaa suuremman parista this-olio, parametriolio! Oikeastaan aika tyylikästä, että kaikki arvot ovat oliota!