Muutettu viimeksi 25.2.2016 / 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:
Samaan tapaan esimerkiksi + on metodi: Sen sijaan että kirjoittaisi 2+3, metodin kutsun voi kirjoittaa myös näin: (2).+(3).
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öä. 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. Näihin palataan.
Huom: Scalan funktiolla voi olla myös vaihteleva määrä parametreja, ns. "toistuvia parametreja". Näihinkin palataan.
Funktionaaliset ohjelmointikielet ovat oikestaan ns. "assign once" -kieliä. Muuttujat siis saavat arvon täsmälleen kerran. Ohjelman suorituksen edetessä muuttujien joukko kuitenkin yleensä kasvaa eli tavallaan ohjelman tila suorituksen edetessä kuitenkin muuttuu! Churchin Lambda-kalkyyli on oikeastaan ainoa "ihan puhdas" funktionaalinen kieli – siinä ei ole lainkaan muuttujia ohjelmointikielten mielessä, "laatikoita joihin sijoitetaan arvo". Nimettyjä funktioitakaan ei siis ole, on vain rakenne, jota esimerkiksi Scalassa kutsutaan funktioliteraaleiksi: argumenttien kuvauksia arvoille.
Listat 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 myös roskienkerääjät alkavat olla melko 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.")Tulostus:
List(1, 2) and List(3, 4) were not mutated. Thus, List(1, 2, 3, 4) is a new list.
Niinpä sen sijaan, että kirjoittaisi
val oneTwoThreeFour = oneTwo ::: threeFour,
voi kirjoittaa myös "tavallisen metodikutsun" tapaan:
val oneTwoThreeFour = threeFour.:::(oneTwo)
Operaatio "::" liittää alkion listan alkuun:
val twoThree = List(2, 3) val oneTwoThree = 1 :: twoThree println(oneTwoThree) // List(1, 2, 3)Tätä operaatiota kutsutaan perinteisesti – jo Lispistä alkaen! – nimellä "cons", koska se konstruoi uuden listan annetusta alkiosta ja annetusta 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 pisteellisinä metodikutsuina:
val oneTwoThree = Nil.::(3).::(2).::(1) println(oneTwoThree)
Kielessä on myös operaatio ":+" alkion liittämiseksi listan loppuun:
val oneTwoThree = List(1, 2, 3) println(oneTwoThree :+ 666) // List(1, 2, 3, 666)Tätä ei kuitenkaan suositella käytettäväksi, koska operaation työläys kasvaa lineaarisesti listan pituuden funktiona!
Listoille on leegioittain operaatioita:
Esimerkkejä operaatioista ja ohjelmointitavoista: [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.sortWith((s, t) => HUOM: sort on deprecated! 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) // 99 println(pair._2) // Luftballons
Tekniikka on toteutettu Scalan piirretyyppien ("trait") periyttämisen avulla. Näistä lisää myöhemmin.
Oletusarvoisesti käytössä oleva Set-versio on "immutaabeli":
var jetSet = Set("Boeing", "Airbus") jetSet += "Lear" println(jetSet.contains("Cessna")) // false val kopioko = jetSet jetSet += "Myrsky" println(kopioko) // Set(Boeing, Airbus, Lear) // EI MUUTOSTA println(jetSet + "Caravelle") // Set(Airbus, Myrsky, Lear, Caravelle, Boeing)
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")) // false val kopioko = jetSet jetSet += "Myrsky" println(kopioko) // Set(Myrsky, Airbus, Boeing, Lear) println(jetSet + "Caravelle") // Set(Myrsky, Airbus, Boeing, Lear, Caravelle) println(kopioko + "Caravelle") // Set(Myrsky, Airbus, Boeing, Lear, Caravelle)
import scala.collection.immutable.HashSet val hashSet = HashSet("Tomatoes", "Chilies") println(hashSet + "Coriander") // Set(Chilies, Tomatoes, Coriander)
Esimakua piirretyypeistä ja luokista:
Myös assosiaatiolistasta on Set-tyyppiä 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)) // Find big X on ground.Ja immutaabeli saadaan ilman importia:
val romanNumeral = Map( 1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V" ) println(romanNumeral(4)) // IV
Huom: Myös -> on metodi. Erimerkiksi 1 -> "I" on mahdollista kirjoittaa muodossa (1).->("I"). Tässä tapahtuu ns. implisiittinen tyyppimuunnos. Niistä myöhemmin.
import scala.io.Source if (args.length > 0) for (line <- Source.fromFile(args(0)).getLines) println(line.length +" "+ line) else Console.err.println("Please enter filename")Ilmaus Source.fromFile(args(0)) avaa (jos voi) komentoriviparametrina 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 println(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!