Scala (llenguatge de programació)
Tipus | llenguatge de programació orientat a objectes, llenguatge de programació funcional, llenguatge de programació multiparadigma, JVM language (en) i llenguatge de programació |
---|---|
Data de creació | 20 gener 2004 |
Disseny | Martin Odersky |
Desenvolupador | Martin Odersky i Escola Politècnica Federal de Lausana |
Paradigma de programació | computació concurrent, programació funcional, llenguatge imperatiu i programació orientada a objectes |
Darrera versió estable | 3.5.0 () |
Llenguatge de programació | Scala |
Influenciat per | ML, Java, Erlang, Haskell, Smalltalk, ML Estàndard, OCaml, Common Lisp (mul) , Llenguatge de programació Eiffel, Oz, Pizza i Scheme |
Extensió dels fitxers | scala i sc |
Codi font | Codi font |
Llicència | llicència BSD i Llicència Apache, versió 2.0 |
Etiqueta d'Stack Exchange | Etiqueta |
Pàgina web | scala-lang.org |
Scala és un llenguatge de programació modern multi-paradigma dissenyat per a expressar patrons de programació generals d'una manera concisa, elegant i segura respecte als tipus. Integra característiques d'orientació a objectes i de llenguatges funcionals.
Scala és orientat a objectes
[modifica]Scala és un llenguatge pur O.O. (orientat a objectes) en el sentit que cada valor és un objecte. Els tipus i comportaments dels objectes són descrits mitjançant classes i 'trets'. L'abstracció classe s'estén mitjançant subclasses i també amb un mecanisme flexible basat en classes 'mixins' (barrejables: similars als interfaces de Java però amb definició de tipus, variables i implementació de mètodes) anomenades trets com a substitució neta de l'herència múltiple.
Scala és funcional
[modifica]Scala també és un llenguatge funcional en el sentit que cada funció és un valor. Scala proveeix una sintaxi fàcil per a la definició de funcions anònimes, suporta funcions d'ordre superior, permet la imbricació de funcions i suporta el currying. Les classes per cas (ang.: case classes) i el suport que inclou de reconeixement de patrons, serveix per a modelar tipus algebraics, emprats en molts llenguatges de programació funcional.
A més a més, la noció de reconeixement de patrons estén de manera natural el procés de dades XML amb l'ajuda de seqüències de patrons ignorant el que hi ha a la dreta (ang.:right-ignoring). En aquest context, les comprehensions de seqüències (iteració sobre Generadors-Filtres-Mapeig) són útils per a formular consultes. Aquestes característiques fan de Scala l'ideal per a desenvolupar aplicacions com a serveis web.
Scala té tipus estàtics
[modifica]Scala està equipat amb un sistema de tipus expressiu que assegura estàticament que les abstraccions s'utilitzin de manera coherent i segura. En particular el sistema de tipus suporta:
- classes genèriques,
- anotacions de variació,
- acotacions de tipus per dalt i per baix,
- classes internes i tipus abstractes com a membres de l'objecte,
- tipus compostos,
- auto referències tipificades explícitament,
- vistes, i
- mètodes polimòrfics.
Un mecanisme d'inferència de tipus permet que l'usuari no hagi d'anotar els tipus de manera redundant. En combinació, aquestes característiques proporcionen una base potent per a la reutilització segura de les abstraccions de programació i també per a l'extensió de programari amb tipus de dades segurs.
Scala és extensible
[modifica]El disseny de Scala reconeix el fet que, a la pràctica, el desenvolupament d'aplicacions d'un domini específic requereix extensions de llenguatge igualment específiques. Scala proporciona una combinació única de mecanismes de llenguatge que fan fàcil afegir noves construccions al llenguatge, amb suavitat, en forma de biblioteques:
- qualsevol mètode pot ser emprat com a operador infix o postfix, i
- els tancaments es construeixen automàticament depenent del tipus esperat.
Un ús conjunt d'ambdues característiques facilita la definició de noves instruccions sense estendre la sintaxi i sense emprar tècniques de meta-programació com ara macros.
Scala interopera amb Java i .NET
[modifica]Scala està dissenyat per a interoperar bé amb entorns populars de programació com Java 2 (JRE) i el marc .NET (CLR). En particular, la interacció amb llenguatges orientats a objectes de gran acceptació com Java i C# és força suau. Scala té el mateix model de compilació (compilació separada, càrrega dinàmica de classes) que Java i C#, permetent accedir a milers de biblioteques d'alta qualitat.
Exemple: Hola món
[modifica]Per començar:
- Al Linux el podeu instal·lar pels paquets de la distribució. Altrament el compilador es pot descarregar d'aquí[1] o com endollable en un entorn integrat de desenvolupament aquí[2]
El programa Hola món en Scala:
// fitxer src/test/hola-mon.scala
package test
object Principal {
def main(args: Array[String]) = Console.println("Hola món!")
}
alternativa derivant l'objecte Application[3] (obsolet des de 2.9, ang:deprecated, eliminat a 2.11) es recomana fer servir el mòdul App
// fitxer src/test/hola-mon.scala
package test
object Principal extends Application { // Application defineix main
// la inicialització del mòdul executa les expressions del cos.
Console.println("Hola món!")
}
El mòdul App afegeix a Application la propietat args que permet accedir als arguments.[4]
// fitxer src/test/hola-mon.scala
package test
object Principal extends App {
Console.println ("Hola món!, els arguments són:" ++ args.mkString (", "))
}
- Compilació i execució a consola de comandes:[5]
mkdir classes # subdirectori per al codi objecte
# compilació (-cp és classpath, -d és directori per a la sortida)
scalac -cp classes -d classes src/test/hola-mon.scala
# execució
scala -cp classes test.Principal # execució pel nom de l'objecte que conté el mètode ''main''
Hola món!
Gestor de projectes SBT
[modifica]Aquesta manera de treballar directament amb el compilador ha quedat una mica antiquada. Si ho fem a través del gestor de projectes SBT,[6] aquest ens descarregarà automàticament la versió del compilador i de les dependències del projecte, esmentades al fitxer de projecte.[7]
Caldrà però seguir l'esquema de directoris especificat per l'SBT.[8]
Al Linux el trobarem al gestor de paquets o bé el podem descarregar d'origen[6] si volem la darrera versió.
A l'avaluador d'expressions (bucle Read-Eval-Print)
[modifica]scala
scala> println("Hola món")
Hola món
scala>
Amb el gestor de projectes SBT podem engegar l'avaluador REPL amb la comanda següent:
// fitxer de projecte build.sbt
lazy val root = (project in file(".")).
settings(
name := "hola_món",
version := "1.0",
scalaVersion := "2.11.7"
)
sbt consoleQuick # engega REPL (Read-Eval-Print Loop) sense carregar les classes del directori de treball i subdirectoris
sbt console # engega REPL carregant les classes del directori de treball i subdirectoris
scala>
- Avaluació d'un mòdul Application (conté mètode main) (obsolet, ang:deprecated)[3]
scala> :reset // reinicialitza l\'avaluador
scala> object Main extends Application { // per accedir als arguments cal redefinir el mètode ''main''
| println("Hola món")
| }
scala> Main // avaluació del mòdul Application, executa les instruccions de l'objecte Main
- Avaluació d'un mòdul App (versió moderna de Application, incorpora membre args).
scala> :reset // reinicialitza l\'avaluador
scala> object Main extends App { // arguments visibles com a ''args'', membre de la classe
| for (arg <- args) {
| println(arg)
|}}
scala> Main.main(Array("a","b","c")) // avaluació del mòdul App, executa les instruccions de l'objecte Main
Característiques
[modifica]Degut a ésser un llenguatge dissenyat inicialment per a la màquina virtual JVM, hereta del Java les característiques del lèxic i els tipus bàsics i les seves operacions així com una sintaxi molt similar.
Els punt-i-coma a final de línia es poden ometre (des de la versió 2).
Vegeu l'especificació del llenguatge.[9]
tipus especials
[modifica]Vegeu Tipus unificats (amb gràfic) a la ref.[10]
- Unit
- Tipus d'un sol valor que és: () // no-op : no fer res (element neutre per l'esquerra de les operacions)
Resultat buit en efectes col·laterals (actualitzacions, impressions). Equival al tipus de retorn buit (void) de Java i C
- Any
- generalització d'un tipus qualsevol
- AnyVal
- generalització dels tipus primaris (els suportats per la CPU) que es passen per valor.
- AnyRef
- generalització dels tipus que es passen per referència. Correspon a Java.lang.Object
- ScalaObject
- base per als objectes de Scala (obsolet, retirat a partir de v2.10)[11]
- Null
- es considera subtipus de qualsevol tipus que es passi per referència (classe o bé object), així el seu valor null pot pertànyer a tots ells.
- Nothing
- Tipus deshabitat (de valor impossible). És subtipus de tots els tipus. Té diversos usos:[12]
- És el tipus de retorn de les rutines que mai no retornen res, és a dir, que sempre acaben llançant excepcions.
- El tipus List[Nothing] és subtipus de totes les llistes (covariants en el tipus de l'elem.) però no pot tenir elements, així el seu únic valor és Nil que és una extensió del tipus List[Nothing]. Això fa de Nil subtipus de List[T] per qualsevol T.[12]
- Symbol
- literals: identificadors precedits per l'apòstrof.[13]
scala> val saluda = 'hola
saluda: Symbol = 'hola
scala> saluda match { // salt de carro després de '{' o bé '(' activa el
// mode multi-línia amb apuntador '|'
| case 'hola => println("Benvingut!")
| case 'adéu => println("A reveure!") } // '}' al mateix nivell, tanca el mode multi-línia
Benvingut!
- Opcionals
- El tipus Option[T] amb les variants Some[T] i None facilita l'ús de paràmetres i resultats opcionals
Es pot convertir un valor possiblement nul de Java amb el generador Option(valorNullificable)
scala> Option(5 :java.lang.Integer)
res1: Option[Integer] = Some(5)
scala> Option(null :java.lang.Integer)
res2: Option[Integer] = None
- Llista
- El tipus List[T]
// llista homogènia
scala> List(1,7,5)
res10: List[Int] = List(1, 7, 5)
// Conversió implícita de tipus (si hi ha una conversió implícita visible)
scala> List(1, 'a', 3.5)
res11: List[Double] = List(1.0, 97.0, 3.5)
// llista especificant un tipus base (més general)
scala> List[Any](1, 'a', 3.5)
res13: List[Any] = List(1, a, 3.5)
// llista buida de tipus sense especificar
scala> List()
res14: List[Nothing] = List()
// Nil especialitza la llista buida
scala> Nil
res16: scala.collection.immutable.Nil.type = List()
scala> Nil : List[Int]
res17: List[Int] = List()
- Tuples (Tuple2 a Tuple22)
scala> val a = Tuple3(1, 'a', 3.5)
a: (Int, Char, Double) = (1,a,3.5)
scala> a._1
res0: Int = 1
scala> a._2
res1: Char = a
Anàlisi per casos dels valors d'una expressió
[modifica]Per encaix primer valors, després variables amb restricció de tipus (de més a menys específic)
package test ;
object Main extends App {
val llista: List[Any] = List(true, 'c', "abc"
, 8:Byte, 270:Short, 300 /* Int */, 200000000000L /* Long */
, 2.5F /* Float */, 3.12345678 /* Double */
) ;
for (elem <- llista) {
elem match {
case 2 | 3 => print("dos o tres") ;
case w: AnyVal => w match { // tipus primitius que es passen per valor
case v :Char => printf("%c u\\%04x", v, v.toInt); // caràcter i hexadecimal
case v :Byte => printf("és un byte: %d", v) ;
case v :Short => printf("és un short: %d", v) ;
case v :Int => printf("és un int: %d 0%o x%08x", v, v, v); // decimal, octal i hexad.
case v :Long => printf("%d", v) ;
case v :Float => printf("%.2f", v) ;
case v :Double => printf("%.8f", v) ;
case v => print(v)
}
case w: AnyRef => w match { // tipus que es passen per referència
case v :String => printf("%s", v) ;
case v => print(v.toString) ;
}
}
}
println("")
}
}
dona
true c u\0063 abc és un byte: 8 és un short: 270 és un int: 300 0454 x0000012c 200000000000 2,50 3,12345678
Igualtat estructural vs. referencial
[modifica]En la comparació d'objectes, l'operador de Scala (==) no correspon en Java al (==) sinó al mètode equals. [14]
Scala | Java | Comentaris | |
---|---|---|---|
igualtat de les referències | eq , ne (classe AnyRef)[15] |
(==), (!=) | |
igualtat estructural | (==), (!=) (classe Any)[16] |
.equals() | excepte per al tipus Array[T][14] |
igualtat dels elements | .sameElements(that: GenIterable[A]) (classe GenIterable)[17] |
permet comparar elems. de diferents estructures |
A Scala la igualtat d'objectes que es passen per referència AnyRef[18] equival a
x == y <=> if (x eq null) (y eq null) else x.equals(y)
polimorfismes
[modifica]polimorfisme per subtipatge
[modifica]trait Animal {
val nom: String
val crit: String
}
class Gat extends Animal {
val nom = "Gat"
val crit = "miola"
}
class Gos extends Animal {
val nom = "Gos"
val crit = "borda"
}
function crida(animal: Animal) {
println (s"El ${animal.nom} ${animal.crit}")
}
polimorfisme paramètric
[modifica]Les funcions es poden parametritzar per Tipus
def nom_funció[VariableDeTipus](param1: VariableDeTipus, ..) = ...
exemple:
object Main extends App {
def imprimeix[T](dada: T) =
dada match {
case v: String => println(v) ;
case v => println (v.toString) ;
}
imprimeix[Int](5) ;
imprimeix[String]("abc") ;
imprimeix(2); // si no s'explicita el tipus, es dedueix dels paràmetres actuals.
imprimeix("def") ;
}
polimorfisme ad-hoc
[modifica]Afegeix requeriments de context al paràmetre de tipus. A l'exemple amb [A: Numeric]
indica que cal que existeixi una instància del genèric Numeric[T] per al tipus A (Numeric[A]), visible en l'àmbit d'ús.
import scala.math.Numeric._
function producte[A: Numeric](x: A, y: A):A = {
val instància = implicitly[Numeric[A]] // instància de Numeric per al tipus del paràmetre emprat a la crida
instància.times(x, y)
}
- scala.math.Numeric és la signatura d'un anell unitari ordenat (Numeric especialitza (extends) Ordering).[19]
- scala.math.Integral és la signatura d'un anell unitari euclidià ordenat íntegre (Integral especialitza (extends) Numeric).[20]
- scala.math.Fractional és la signatura d'un cos ordenat (Fractional especialitza (extends) Numeric).[21]
- quan hi ha una sola instància, equival a la crida amb paràmetre implícit (vegeu #Implícits)
- quan hi ha més d'una instància d'un genèric (ex. diversos monoides), cal fer servir la crida amb implícit explicitant la instància desitjada
abstract class SemiGrup[A] {
def assoc(x: A, y: A): A
}
abstract class Monoid[A] extends SemiGrup[A] {
def neutre: A
}
object Monoides {
// Monoides per a la suma i el producte
implicit object IntMonoidSuma extends Monoid[Int] {
def assoc(x: Int, y: Int): Int = x + y
def neutre: Int = 0
}
implicit object IntMonoidProducte extends Monoid[Int] {
def assoc(x: Int, y: Int): Int = x * y
def neutre: Int = 1
}
}
object Test extends App {
import Monoides._ // per fer servir les instàncies implícites, han d'estar visibles
import scala.collection.Iterable
def plegaLlista[A](xs: Iterable[A])(implicit m: Monoid[A]): A = xs.fold (m.neutre) (m.assoc)
println(plegaLlista(List(1, 2, 3)) (IntMonoidSuma))
println(plegaLlista(List(1, 2, 3)) (IntMonoidProducte))
}
àlies de tipus
[modifica]type LaMevaFun[A,B,C] = B => A => Pair[B, C]
Relacions de Subtipus
[modifica]A <: B
indica A subtipus de BA >: B
indica B subtipus de A
En el següent exemple Tipus ha d'ésser subtipus de TCotaSuperior.[22]
type Tipus <: TCotaSuperior // 'Tipus' ha d'ésser subtipus (més especialitzat) de 'TCotaSuperior'
en el següent exemple Tipus ha d'ésser superclasse de TCotaInferior.[23]
type Tipus >: TCotaInferior // 'Tipus' ha d'ésser supertipus (més general) de 'TCotaInferior'
Subtipatge de funcions i de classes, Covariança, Contravariança
[modifica]- Una funció f: (A' => B') és subtipus d'una altra funció g: (A => B) (f pot ser emprada en el lloc de g) si el resultat és de tipus més especialitzat
(B' <: B)
i admet arguments de tipus més general(A' >: A)
.
Es diu que la funció f subtipus de g és covariant en el resultat (la relació varia en el mateix sentit) i contravariant en el paràmetre de valor. El tipus funció definit a Predef és
// el prefix + en un paràmetre de tipus indica covariança
// el prefix - en un paràmetre de tipus indica contravariança
// del mòdul Predef que conté els elements predefinits del llenguatge
type Function[-A, +B] = (A) ⇒ B
Perquè una classe C sigui subtipus d'una altra D, és a dir poder fer servir C com a tipus del paràmetre actual en una crida amb paràmetre formal de tipus D, C ha de contenir subtipus de tots els mètodes de D, a banda d'altres requeriments.
Això ens facilita la deducció de la relació de subtipatge de les col·leccions respecte de la que hi hagi entre els tipus dels elements.
- La covariança d'una classe d'una col·lecció respecte del tipus de l'element es dona si:
Sempre que A <: B
llavors C[A] <: C[B]
Així sabem que les llistes, són covariants respecte al tipus de l'element, i mantenen la relació de subtipatge entre llistes d'elements de tipus diferents, si hi és entre els tipus dels elements.
- La contravariança d'una classe es dona si:
Sempre que A <: B
llavors C[A] >: C[B]
Perquè una classe C[T] sigui covariant en un tipus T, C[+T], aquest tipus només pot aparèixer en posicions covariants (resultats) dels mètodes i no en les contravariants (arguments).[24][25] Al revés per a les classes contravariants en T: C[-T]. Però sembla que és una condició necessària però no suficient. Vegeu el Principi de substitució de Liskov
Per utilitzar un tipus covariant en posicions d'argument (contravariant) en un mètode i continuar mantenint la covariança del mètode i, per tant, de la classe, cal fer servir el truc del límit inferior.[25] Cal assegurar que l'argument ha d'admetre tipus més generals que T, fent servir per a l'argument un tipus existencial: aquells tipus U tals que U >: T
.
class ListNode[+T](h: T, t: ListNode[T]) {
def head: T = h
def tail: ListNode[T] = t
// per poder utilitzar el tipus T en l'argument i mantenir la covariança de la classe respecte a T
// l'argument (posició contravariant) ha d'admetre qualsevol supertipus U >: T
def prepend[U >: T](elem: U): ListNode[U] =
ListNode(elem, this)
}
Tipus producte
[modifica]Tuples (tipus producte anònim)
[modifica]Definides les classes Tuple2, Tuple3, .., Tuple22.
object Main extends App {
val parell = new Tuple2(10, "abc") ; // new amb el nom de la classe
val Tuple2(primer, segon) = parell; // encaix de la tupla
println(parell._1) ; // primer
println(parell._2) ; // segon, ...
}
Classes (tipus producte amb nom), i Objectes singulars
[modifica]A Scala se separen les parts dinàmica i estàtica de les classes del Java en class i object, objecte singular (únic) que conté els membres estàtics.
Extensibilitat. Els object tenen un tractament unificat amb les classes respecte a l'extensibilitat com si fossin classes d'una sola instància, que els permet implementar interfícies i estendre-les amb altres object i traits com a l'exemple amb l'objecte App.
Grafia. Els identificadors dels mòduls object comencen per majúscula segons el llibre d'estil.
La inicialització dels object i també de les classes, es produeix seqüencialment processant definicions i instruccions executables, com a l'ML Estàndard i l'OCaml.
// ''App'' és l'object d'engegada que conté el mètode ''main''
object ProvaFiltratge extends App {
def filtrar(xs: List[Int], llindar: Int) = {
def processa(ys: List[Int]): List[Int] = // funció a l'interior d'una altra funció
if (ys.isEmpty) ys
else if (ys.head >= llindar) ys.head :: processa(ys.tail)
else processa(ys.tail)
processa(xs)
}
println(filtrar(List(1, 9, 2, 8, 3, 7, 4), 5))
}
En importar una classe de Java, l'identificador designa la classe equivalent en Scala amb els mètodes dinàmics (de la instància), així com l'Object que conté els mètodes, variables i constants estàtics.
import java.lang.{Color}
val vermell = new Color(0xFF, 0, 0); // constructor de la ''classe''
val componentRoigDelBlanc = Color.getRed(Color.WHITE); // membres estàtics de l{{'}}''Object''
- els object (mòduls amb els membres estàtics) de les col·leccions incorporen generadors
object List {
def apply[T] (elements: T*) // genera una llista amb els elements proporcionats com a paràmetres
// ''apply'' és el nom de funció per omissió (es pot ometre)
// així List (1, 2, 3) equival a List.apply(1, 2, 3)
def empty[T] () // generador de col·lecció buida
}
- override
- els mètodes que es redefineixen han de dur el qualificatiu override per distingir-los dels que es volen nous, per evitar que si algú afegeix un mètode amb el mateix nom i signatura a alguna de les superclasses o trets que s'hereten, el comportament esdevingui diferent del que se n'espera.
- val/var (al paràmetre de la classe)
- l'ús de declaradors val/var converteix el paràmetre en un camp membre de la classe, evita redefinir-lo com a camp amb el mateix nom, i haver d'assignar-li el valor del paràmetre dins el constructor.
class Classe(val p1: Tipus, p2: Tipus2 = vPerDefecte, ...) // amb val, p1 passa a ser membre de la classe
Extractors
[modifica]Un extractor és un objecte singular que implementa la funció unapply que permet extreure'n el valor en encaixar patrons en les opcions de les clàusules match.[26]
// el següent codi genera un objecte extractor
object Doble {
// generador
def apply (x: Int): Int = x * 2
// inversa per als encaixos de patrons, retorna en un Option l'èxit o fracàs de l'encaix
def unapply (z: Int): Option[Int] = if (z % 2 == 0) Some (z/2) else None
}
object ProvaDoble extends App {
val x = Doble (21) // aplica 'apply' (nom de funció per omissió a les expressions)
x match { case Doble (n) => Console.println(n) } // aplica 'unapply' (nom de funció per omissió als patrons)
}
tipus "suma" amb classes encaixables
[modifica]Les classes encaixables (ang:case class) permeten definir com a subtipus les variants d'un tipus suma (unió discriminada) permetent l'anàlisi per casos.
Les classes encaixables són classes per a les quals es defineix, automàticament, un objecte extractor (secció precedent), facilitant l'ús del nom de la classe i els seus paràmetres com a patró en un selector per encaix de patrons match/case.
abstract class Arbre[T]
// ''val'' al paràmetre el declara membre de la classe, estalvia redeclaracions
case class Fulla[T](val dada: T) extends Arbre[T]
case class Branca[T](val dada: T, val esquerra: Arbre[T], val dreta: Arbre[T]) extends Arbre[T]
object Main extends App {
def imprimeixArbre[T](arbre: Arbre[T]):Unit =
arbre match {
case Fulla(d) => println ("fulla: " ++ d.toString)
case Branca(d, esq, dreta) => { imprimeixArbre(esq) ;
println("branca: " ++ d.toString) ;
imprimeixArbre(dreta) ;
}
}
// els constructors de case classes no necessiten ''new'', de fet no són constructors sinó generadors membres de l'objecte extractor
// l'expressió {Fulla(1)} equival a {Fulla.apply(1)} // ''apply'' és el nom de funció per omissió.
val arbre1 = Fulla[Int](1);
// si s'omet el paràmetre de tipus ''[T]'', es pren el tipus del paràmetre actual.
val arbre2 = Branca(2, arbre1, arbre1) ;
imprimeixArbre(arbre2) ;
}
Equivalència en ML Estàndard:
datatype 't Arbre = Fulla of 't | Branca of ('t * 't Arbre * 't Arbre)
Equivalència en Haskell:
data Arbre t = Fulla t | Branca t (Arbre t) (Arbre t)
exhaustivitat als encaixos (atribut sealed)
[modifica]L'atribut sealed (segellat) a la classe abstracta de la qual es deriven les variants encaixables (case classes), permet circumscriure'n la definició de variants al fitxer on està definida, formant un conjunt tancat, facilitant que el compilador pugui avisar per manca d'exhaustivitat als encaixos.
Exemple de compilació amagant el cas Fulla:
sealed abstract class Arbre[T] // 'sealed': classe segellada, per al control d'exhaustivitat als encaixos
case class Fulla[T](val dada: T) extends Arbre[T]
case class Branca[T](val dada: T, val esquerra: Arbre[T], val dreta: Arbre[T]) extends Arbre[T]
object Main extends App {
def imprimeixArbre[T](arbre: Arbre[T]):Unit =
arbre match {
// case Fulla(d) => println ("fulla: " ++ d.toString) // comentat expressament per veure l'avís del compilador
case Branca(d, esq, dreta) => { imprimeixArbre(esq) ;
println("branca: " ++ d.toString) ;
imprimeixArbre(dreta) ;
}
...
sbt compile
[info] Compiling 1 Scala source to ...
[warn] /home/gabi64/lleng/scala/sealed/Main.scala:9: match may not be exhaustive.
[warn] It would fail on the following input: Fulla(_)
[warn] arbre match {
[warn] ^
[warn] one warning found
[success] Total time: 10 s, completed 07/06/2016 15:35:50
Seqüències numèriques
[modifica]- Range // classe de seqüències de Int
- Per generalitzar-ho per a enters de qualsevol precisió {Long, Int, Short, Byte}
NumericRange[T: Integral] // classe de seqüències de tipus que implementen Integral[T] (anell unitari íntegre ordenat euclidià: operacions dels enters)
Les següents expressions retornen un iterador, que es pot fer servir com a generador en una clàusula for, o bé aplicar toList per obtenir-ne una llista.
(1 until 5) // interval obert (exclou límit superior)
(1 to 5) // interval tancat (inclusiu)
(1 to 5 by 2) // interval amb increment
Llistes
[modifica]object Main extends App {
val llista = (1 to 3).toList ; // List(1,2,3)
def descriuLlista[T](llista: List[T]) =
llista match {
case Nil => println ("llista buida") ;
case ll @ (_ :: _) => // as-pattern (variable @ patró): unifica variable i terme encaixat
println ("llista no buida: " + ll.toString) ;
}
descriuLlista(llista)
}
La clàusula for admet una primera part amb generadors i filtres (clàusula if), i una segona part que pot ésser o bé un bloc d'efectes col·laterals sobre els elements del generador, o bé una clàusula yield (trad.:produeix) que mapeja l'expressió o bloc als elements del primer generador retornant la col·lecció resultant.
// retorna els dobles dels parells de l'interval
// en una col·lecció del tipus del primer generador
val iterador = for (i <- Iterator.range(des_de, topall_exclòs)
if i % 2 == 0
) yield 2 * i // el tipus resultant és Iterator[Int]
println (iterador.toList)
// equivalent amb rangs: (des_de to topall_inclòs)
val iterador = (1 to 12).iterator.filter(_ % 2 == 0).map(_ * 2)
println (iterador.toList)
Les llistes per comprensió introdueixen l'encadenament monàdic: encadenament d'una acció amb una funció d'efectes col·laterals sobre el resultat precedent, el tipus final és el de l'acció primera amb resultat determinat per la funció (avaluada en segon lloc). L'acció també és assimilable a l'avaluació d'una col·lecció amb el tipus de l'element com a tipus del resultat.
La clàusula for incorpora una seqüència de generadors, guardes (clàusula if), expressions basades en resultats precedents (clàusula let) i cos, que es tradueix en l'ús de les primitives flatMap, filter i finalment map per al cos, retornant sempre una col·lecció del tipus del primer generador, amb elements del tipus del resultat del cos.
scala> val opt3 = Some(3)
scala> val opt4 = Some(4)
scala> for { x <- opt3;
y <- opt4 } yield (x+y)
res4: Option[Int] = Some(7)
// desplegament monàdic equivalent
scala> opt3 flatMap { x => opt4 map { y => x + y}}
res5: Option[Int] = Some(7)
Per als tipus amb "efecte fallada" (element absorbent per a flatMap), com ara Option[T], Try[T], List[T], ..., l'encadenament de generadors, filtres i expressions evita la necessitat de consultar la correcció del resultat a cada pas, perquè l'encadenament progressa només per als elements no absorbents per l'esquerra respecte a l'encadenament (aquí l'op. flatMap).
-- ús de l'encadenament monàdic per al tipus Try[T] (variants Success[T] i Failure[Throwable])
-- Try encapsula, a l'exemple, l'avaluació de l'operació de lectura, retornant Failure(err) en cas d'excepció
-- Failure(..) és l'element absorbent per l'esquerra de ''flatMap'' per al tipus Try, fent que l'encadenament no progressi.
val result = for (dividend <- Try(Console.readLine("entreu dividend: ") ;
divisor <- Try(Console.readLine("entreu divisor: ")
) yield dividend / divisor
result match {
case Success(v) => println ("correcte: " + v.toString)
case Failure(err) => println ("pifiada: " + err.getMessage)
}
Vegeu presentació "Introduction to Monads in Scala".[27]
Variables i mètodes
[modifica]El tipus de les declaracions es pot ometre si es dedueix de la seva inicialització de manera inequívoca, altrament el compilador avisarà.
val identificador :Tipus = expressió // amb val: assigna el valor de l'expressió com a constant
var identificador :Tipus = expressió // amb var: variable mudable
def identificador :Tipus = expressió // amb def: es recalcula a cada invocació
// Les inicialitzacions de variables amb el comodí '_'
// assignen l'element zero del tipus si és subtipus de 'AnyVal' (pas per valor)
// o bé 'null' si és subtipus de 'AnyRef' (pas per referència)
scala> var v : Boolean = _
v: Boolean = false
scala> var v : Int = _
v: Int = 0
scala> var v : String = _
v: String = null
La diferència entre una definició amb val i un mètode (definit amb def) és que l'assignació amb val es calcula una única vegada, mentre que amb def es recalcula a cada invocació.
Operadors
[modifica]Els operadors són mètodes definits amb símbols.
class Q {
def ##(that:Q) = this != that
}
La precedència la determina el primer caràcter del símbol. (Vegeu apartat 'Infix operators' al manual de ref. del llenguatge).
(all letters) | ^ & < > = ! : + - * / % (all other special characters)
associativitat per la dreta explícita
[modifica]L'associativitat la determina el darrer caràcter del símbol, que si és ':' llavors és associatiu per la dreta, altrament associatiu per l'esquerra. Vegeu ScalaReference.pdf (paràgraf 6.12.3 Infix Operations).
L'associativitat per la dreta, en agrupar per la dreta, requereix que l'operador en notació infix s'avaluï com a mètode del tipus de l'operand de la dreta:
// si 'x1' i 'x2' són elements i 'xs' és una col·lecció
x1 +: x2 +: xs ≡ x1 +: (x2 +: xs) // (+:) ha de ser mètode del tipus de l'operand de la dreta: la col·lecció
L'associativitat per la dreta pels ':' finals, afecta a més dels operadors, als constructors definits amb símbols, com ara (::) el "Cons" de les llistes i també (#::) el "Cons" dels Stream.
// (++:) concatenació amb associativitat per la dreta (pels ':' final)
// (++:) és membre del tret "Traversable", que haurà d'implementar l'operand de la dreta
// StringBuilder és una ''String'' mudable que implementa ''Growable'' (que pot créixer).
// seq.mkString converteix una seqüència en una String
val strBldr = "abc" ++: "def" ++: new StringBuilder("ghi")
val novaStr = strBldr.mkString
Trets (ang:traits): el sistema d'extensió de classes i d'objectes singulars
[modifica]La paraula anglesa trait es pot traduir per tret o característica (vegeu diccionaris d'anglès).
Un tret encapsula una característica incorporable a una classe d'objectes o bé a un object singular.
Són assimilables als interface del Java, però amb el potencial d'una classe abstracta.
Els trets poden contenir estructura (camps), comportament (mètodes) i tipus associats, tant abstractes com concrets (amb implementació), però no són instanciables per si mateixos, ni podien tenir paràmetres de valor. Això canviarà pròximament permetent els paràmetres als trets.[28]
import java.awt.{Color} ;
trait Acoloriment {
var color:Color;
def pinta(nouColor: Color): Unit = { color = nouColor}
def imprimeixColor: Unit = { print("color: "); println(color) }
}
trait Cloneable extends java.lang.Cloneable {
override def clone(): Cloneable = { super.clone(); this }
}
trait Resetable {
def reset: Unit
}
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
val cloned = obj.clone()
obj.reset
cloned
}
herència múltiple
[modifica]L'herència múltiple es basa en la incorporació de trets.
import java.awt.{Color} ;
class Punt(xInicial:Int) {
var x: Double = xInicial.toDouble ;
def desplaça(dx: Double): Unit = { x = x + dx}
def imprimeixPosició: Unit = { print("posició: "); println(x) }
}
// els trets (ang: trait) són com una classe abstracta
trait Acoloriment {
var color: Color;
def pinta(nouColor: Color): Unit = { color = nouColor}
def imprimeixColor: Unit = { print("color: "); println(color) }
}
// incorpora el tret Acoloriment donant valor als membres abstractes
class PuntAcolorit(xInicial:Int, colorInicial: Color) extends Punt(xInicial) with Acoloriment {
var color = colorInicial ;
def desplaçaIPinta(dx: Double, nouColor: Color) = {
super[Punt].desplaça(dx) ;
super[Acoloriment].pinta(nouColor) ;
}
}
object Main extends App {
val punt = new PuntAcolorit(0, Color.WHITE) { // extensió en instanciar
// només aquesta instància incorpora l'extensió
def desplaçaRàpid(dx: Double) = {
x = x + 10.0 * dx
}
}
punt.desplaçaIPinta(2, Color.BLUE) ;
punt.desplaçaRàpid(3.5)
punt.imprimeixPosició ;
punt.imprimeixColor ;
}
dona el resultat:
posició: 37.0 color: java.awt.Color[r=0,g=0,b=255]
Restricció del tipus propi (ang:self-types)
[modifica]La restricció al tipus propi en un tret (especificant un identificador, com ara self, abans de =>) permet relacionar un tret amb una instància concreta d'un tipus abstracte.[29] És possible utilitzar this per aquest afer, però en cas de referir-s'hi en una classe interna (niuada) l'identificador this quedaria tapat. this es referiria a l'objecte corresponent a la classe interna i no a l'externa.
Del document "Scala Component Abstractions" de M.Odersky.[30]
abstract class Graf {
type Node <: NodeBase; // volem que els nodes implementin NodeBase
class Arc(val origen: Node, val destí: Node) {}
class NodeBase {
def connectaAmb(n: Node): Arc =
new Arc(this, n); // il·legal !! 'this' no és de tipus Node
}
}
Solució amb self-types
abstract class Graf {
type Node <: NodeBase; // volem que els nodes implementin NodeBase
class Arc(val origen: Node, val destí: Node) {}
abstract class NodeBase {
self: Node => // requeriment que el tipus propi sigui subtipus de Node en obtenir-ne una classe concreta
def connectaAmb(n: Node): Arc =
new Arc(self, n); // ara si que passa la compilació
}
}
// classe concreta de Graf que incorpora la classe concreta Node
class GrafEtiquetat extends Graf {
class Node(etiq: String) extends NodeBase {
def getEtiqueta: String = etiq;
}
}
Anàlisi per casos del subtipatge per especialització
[modifica]Refinament de tipus (ang: Downcasting) al d'una classe derivada, amb caracterització mitjançant asInstanceOf prèvia comprovació amb isInstanceOf.
def fun(param: Tipus) = if (param.isInstanceOf[Subtipus]) {
val subtipParam = param.asInstanceOf[Subtipus];
... }
else ...
Enumeracions
[modifica]object Main extends App {
object DiaSetm extends Enumeration {
type DiaSetm = Value
val Dl, Dt, Dc, Dj, Dv, Ds, Dg = Value
}
import DiaSetm._
def ésDiaFeiner(d: DiaSetm) = ! (d == Ds || d == Dg)
println (DiaSetm.values filter (ésDiaFeiner) mkString (", "))
}
$ sbt run
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; support was removed in 8.0
[info] Set current project to enums (in build file:/home/gabi64/lleng/scala/enums/)
[info] Compiling 1 Scala source to /home/gabi64/lleng/scala/enums/target/scala-2.10/classes...
[info] Running Main
Dl, Dt, Dc, Dj, Dv
[success]
Parametrització de tipus amb cota superior
[modifica]Admetre tipus més especialitzats que un tipus determinat.
trait Similar {
def isSimilar(x: Any): Boolean
}
case class MyInt(x: Int) extends Similar {
def isSimilar(m: Any): Boolean =
m.isInstanceOf[MyInt] &&
m.asInstanceOf[MyInt].x == x
}
object UpperBoundTest extends App {
def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
if (xs.isEmpty) false
else if (e.isSimilar(xs.head)) true
else findSimilar[T](e, xs.tail)
val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
println(findSimilar[MyInt](MyInt(4), list))
println(findSimilar[MyInt](MyInt(2), list))
}
funcions amb llista d'arguments variable
[modifica]El símbol '*' després del tipus del darrer argument, indica que el paràmetre actual corresponent pot aparèixer zero o més vegades, i que cal prendre aquest argument com una seqüència.
def imprimeix(args: String*) = {
for(arg <- args){
println(arg);
}
}
imprimeix("abc", "def", "ghi")
funcions anònimes com a expressió
[modifica]exemple:
(x: Int, y:Int) => x + y // el tipus del resultat és deduït per inferència en aquest cas
En l'exemple següent el compilador dedueix els tipus dels paràmetres de la funció sobre la qual s'aplica.
object Main extends App {
val llista = List(1,2,3,4); // pels literals infereix tipus List[Int]
// aplica la funció a un acumulador i cada valor de la llista
// els tipus dels paràmetres els dedueix de l'aplicació parcial (funció ometent paràmetres) precedent.
// tipus de llista.foldleft(0) : (Int * Int) => Int
val result = llista.foldLeft(0) { (x,y) => x+y };
Console.print("resultat: ") ;
Console.println(result);
}
dona l'esperat
resultat: 10
L'aplicació parcial d'operadors s'expressa substituint un o més operands pel comodí '_' en una expressió. Ex. (3 + _) : (Int => Int)
- També es pot referir a mètodes:
(_.toString) : (Any => String)
scala> :type List[Any](1, 2.5, "abc") .map (_.toString)
List[String]
scala> def floatMes3: (Float => Float) = (_ + 3) // cal la restricció de tipus
floatMes3: Float => Float
Aplicació parcial d'una funció
[modifica]Per obtenir el valor de funció dels mètodes def d'un object (que són mètodes estàtics) i poder passar-lo com a paràmetre a una funció d'ordre superior, cal substituir els paràmetres no aplicats amb un únic comodí '_'.
Scala no té polimorfisme d'ordre superior (ang:impredicative polymorphism), per tant les funcions que es passen com a paràmetre no poden ser polimòrfiques (cal donar valor als seus paràmetres de tipus).
// Definint ''flip'' (intercanviar l'ordre d'arguments) per fer-lo servir més tard
scala> def flip[T1, T2, R](f : T1 => T2 => R) (x: T2) (y: T1) : R = f (y) (x)
flip: [T1, T2, R](f: T1 => (T2 => R))(x: T2)(y: T1)R
scala> def cons[A] (x: A) (xs: List[A]) = x :: xs
cons: [A](x: A)(xs: List[A])List[A]
scala> :type cons[Int] (5) _ // tipus aplicant només un paràmetre
List[Int] => List[Int]
scala> :type cons[Int] _ // tipus aplicant zero paràmetres
Int => (List[Int] => List[Int])
scala> flip (cons[Int] _) (List(2,3)) (1) // passant 'cons' a 'flip' (funció d'ordre superior)
res0: List[Int] = List(1, 2, 3)
Composició i currificació de funcions
[modifica]La interfície Function1 de funcions d'un argument aporta mètodes per la composició de funcions.[31]
La interfície Function2 de funcions de dos arguments aporta mètodes per obtenir versions currificades (.curried) i la inversa (.tupled).[32]
Function3 a Function19 també definides.
package test
object Main extends App {
// composició de les funcions d'un arg.
def doble(x: Int) = x * 2
def suma3(x: Int) = x + 3
// f compose g == f. g
// f andThen g == g. f
// cal especificar '_' per obtenir l'aplicació parcial
def doblaiSuma3 = (doble _).andThen(suma3 _) // composició d'esquerra a dreta
def doblaiSuma3 = (suma3 _).compose(doble _) // composició amb estil matemàtic, resultat idèntic a l'anterior
val prova = doblaiSuma3(5)
// currificació i aplicabilitat als arg. en tupla
def cons[A] (x: A, xs: List[A]) = x :: xs
def f[T] = (cons[T] _) // cal especificar '_' per obtenir l'aplicació parcial
val x1 = f (1, List(2,3))
// currificació (amb 'curried'' la funció s'aplica als param. separats
val x2 = f[Int].curried (1) (List(2,3)) // cal explicitar el paràmetre actual de tipus
}
Amb funcions als paràmetres o al resultat.
// en aquest cas, funció que pren una funció d'argument i en retorna una altra
def sum(f: Int => Int): (Int, Int) => Int = {
def sumF(a: Int, b: Int): Int = // suma f(x), x <- [a..b]
if (a > b) 0 else f(a) + sumF(a + 1, b)
&sumF
}
def sumaDobles = &sum(x => 2 * x)
def sumaQuadrats = &sum(x => x * x)
Definició per patrons d'una funció - El tret PartialFunction
[modifica]Vegeu "Pattern-matching anonymous functions".[33]
def or : (Boolean, Boolean) => Boolean =
{ case (true, _) => true;
case (false, x) => x
}
Equival a la definició per encaixos dels llenguatges funcionals (ex.: Haskell) amb un sol paràmetre i un resultat.
Una definició per seqüència de patrons, si no són exhaustius, és una funció definida parcialment.
Les funcions amb definició per patrons implementen el tret PartialFunction[A /*entrada*/, B /*resultat*/]
,[34] amb mètodes molt interessants.
val pf: PartialFunction[A, B] = { case patró1 if condició => ...;
case patró2 => ... ;
... }
La funció pf.isDefinedAt(x:A) retorna si el paràmetre encaixa en alguna de les clàusules case (filtres compresos) !!
El mètode lift com a pf.lift embolcalla el resultat en un Option oferint una versió total de la funció parcial.
El mètode applyOrElse permet especificar una funció per defecte per als casos no definits.
pf.applyOrElse(x, funcPerDefecte) ≡ if(pf isDefinedAt x) pf(x) else funcPerDefecte(x)
El mètode orElse admet la composició amb una altra funció parcial, que s'avalua per als casos no definits de la primera.
scala> val dobleDeParell: PartialFunction[Int, Int] = {
| case x if x % 2 == 0 => 2 * x
| }
dobleDeParell: PartialFunction[Int,Int] = <function1>
scala> dobleDeParell.isDefinedAt(3)
res0: Boolean = false
scala> dobleDeParell.isDefinedAt(2)
res1: Boolean = true
scala> dobleDeParell.lift(3)
res2: Option[Int] = None
- A les col·leccions:
collect mapeja amb una funció parcial obtenint la col·lecció de les imatges d'aquells elements per als quals la funció està definida:
// interfície GenTraversable
def collect[B](pf: PartialFunction[A, B]): Repr[B]
// interfície GenTraversableOnce
def collectFirst[B](pf: PartialFunction[A, B]): Option[B] // obté, si existeix, la primera de les imatges de mapejar amb una funció parcial
Excepcions
[modifica]Excepcions en la Gestió de recursos
[modifica]La clàusula try {..} catch (err:Throwable) {..} finally {..} possiblita la gestió d'excepcions i alliberament de recursos.
L'objecte Exception[35] aporta diverses possibilitats addicionals.
gestió automàtica
[modifica]La recent construcció a Java 7 try-with-resources[36] que encapsula el recurs i el seu alliberament ha fet repensar una clàusula semblant a Scala. Per a l'anomenat "Loan pattern" o patró del préstec (perquè el recurs s'ha de tornar),[37] es proposa la següent construcció (Closeable[38] és una interfície de Java)
def using[A, R <: Closeable](r : R)(f : R => A) : A = {
var result : A = _
try {
result = f(r)
} finally {
try {r.close()}
catch {case e: IOException => ()}
}
result
}
La biblioteca scala-arm[39] proporciona una implementació a través de la implementació monàdica de les clàusules for, que evita el niuament de construccions en cas d'adquisició de múltiples recursos.
import resource._
// dos recursos, còpia de input a output, amb alliberament automàtic
for(input <- managed(new java.io.FileInputStream("test.txt");
output <- managed(new java.io.FileOutputStream("test2.txt")) {
val buffer = new Array[Byte](512)
def read(): Unit = input.read(buffer) match {
case -1 => ()
case n => output.write(buffer,0,n); read()
}
read()
}
Anàlisi per casos de l'èxit o fracàs de l'avaluació. La classe Try
[modifica]El generador Try(expr) avalua una expressió que pot llançar excepcions, permetent analitzar per casos l'èxit o fracàs de l'avaluació, oferint com a resultat dues variants d'encaix (case classes) de la classe Try[T] que són Success(resultat:T) i Failure(excepció:Throwable).
A més implementa la classe de tipus Mònada amb (Failure(e:Throwable)) com a element absorbent de l'encadenament, de manera que en una clàusula for (avaluació monàdica) la fallada fa que no s'avaluïn les clàusules subsegüents del for evitant la necessitat de comprovació de l'èxit a cada pas.
A l'exemple el tipus del for és com sempre el del primer generador, en aquest cas un tipus Try.
val res = for (dividend <- Try(Console.readLine("entreu dividend: ") ;
divisor <- Try(Console.readLine("entreu divisor: ")
) yield dividend / divisor
res match {
case Success(v) => println ("correcte: " + v.toString)
case Failure(err) => println ("error de lectura (del readLine) " + err.getMessage)
}
Disseny per Contracte
[modifica]Precondicions, postcondicions i invariants
Assercions
[modifica]Vegeu Assercions[40] i Opció de compilació -enableassertions[5]
- assert(predicat [,missatge])
- especifica un predicat que cal provar i dispara l'excepció java.lang.AssertionError: assertion failed: missatge
- assume(axioma [,missatge])
- especifica un axioma (asserció que les eines de comprovació estàtica de tipus no han de deduir de proposicions precedents). En temps d'execució actua com un assert disparant l'excepció java.lang.AssertionError: assumption failed: missatge
- error(missatge)
- per al cas d'estats inconsistents o bé de funció no definida per un cas dels paràmetres
- ???
- indica implementació pendent, dispara UnsupportedOperationException("not implemented").[41]
def mètode : Tipus = ??? // mètode pendent d'implementar (per poder passar la compilació)
Precondició
[modifica]La clàusula require ha estat declarada obsoleta i retirada a la v2.8.[42]
Es recomana fer servir la definició per patrons per especificar les precondicions.
Postcondició
[modifica]- {acció} ensuring (_ => condició [,missatge])
- asserció com a funció sobre el resultat d'un bloc de codi.
// malament a posta, per fer petar la postcondició
scala> def incrementa(x: Int) = { x - 1 } ensuring (_ > x, "el resultat no és superior")
incr: (x: Int)Int
scala> incrementa (5)
java.lang.AssertionError: assertion failed: el resultat no és superior
at scala.Predef$Ensuring$.ensuring$extension3(Predef.scala:256)
at .incr(<console>:10)
... 43 elided
Anàlisi per casos de l'avaluació de funcions parcials
[modifica]És interessant evitar les funcions que disparen error() degut a paràmetres il·legals o estats inconsistents, i obtenir-ne versions amb resultat opcional (Option[T]) que obliga a analitzar el resultat a la rutina que fa la crida.
Vegeu també #Definició per patrons d'una funció - El tret PartialFunction
Igualment les funcions que disparen excepcions poden ser avaluades amb Try(expressió) oferint resultat de tipus Try[T] que retorna o bé el resultat, amb la variant Success[T](v:T), o bé l'excepció amb Failure(e:Throwable), evitant que aquesta es propagui més amunt.
Admet notació de llenguatge de marques XML com a literal, amb interpolació d'expressions entre claus "{}". Vegeu[43]
object Main extends App {
val idioma = "ca" ;
val títol = "El meu títol" ;
val xmlTítol = <title> {títol} </title>; // entre claus '{' <expr> '}' interpolació d'expressions.
val xmlCapçalera = <head> {xmlTítol} </head>;
val xmlCos = <body> Aquesta pàgina duu per títol: {títol} </body> ;
// substitució a l'atribut, hi afegeix les cometes.
val xmlPàgina = <html lang={idioma}> {xmlCapçalera} {xmlCos} </html> ;
// prefix
val capsalXml ="<?xml version='1.0' encoding='UTF-8' ?>\n" ;
val doctype = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " +
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" ;
println(capsalXml + doctype + xmlPàgina) ;
}
El paquet de tractament de XML scala.xml, que anteriorment formava part de la biblioteca base de Scala, ha estat separat en un paquet Jar independent que es pot obtenir amb el codi font al GitHub d'aquí.
Implícits
[modifica]Scala té diversos mecanismes relacionats amb el tipus que busquen automàticament entre les instàncies visibles en l'àmbit,
- o bé la conversió implícita de tipus,
- o bé, en cas d'un genèric de tipus (classe de tipus), la implementació corresponent al tipus emprat (tipus del paràmetre actual).
Vistes de tipus (conversions implícites)
[modifica]Si el tipus del paràmetre actual no correspon amb el del paràmetre formal, però existeix una funció de conversió implícita entre ambdós tipus:
// del Predef
// char2int permet emprar valors de tipus Char en paràmetres Int
implicit def char2int (x: Char): Int
Restricció de Convertibilitat (View bound)
[modifica]És una alternativa menys estricta que un requeriment de subtipatge.
type A <% T
// indica que cal que existeixi una conversió implícita de A a T. Vegeu Scala Views[44]
trait Set[A <% Ordered[A]]... // consulteu la ref.
Instàncies implícites de classes de tipus
[modifica]El mecanisme dels implicit objects permet obtenir la instància (implementació) d'una classe de tipus (classe abstracta genèrica parametritzada per un tipus) per al tipus del paràmetre actual.
A l'exemple el paràmetre (implicit m: Monoid[A]), m obtindrà el valor de la instància específica del genèric Monoid[A] per al tipus del paràmetre actual emprat en la crida a la funció def sumaLlista[A](xs: List[A])(implicit m: Monoid[A]).
abstract class SemiGrup[A] {
def suma(x: A, y: A): A
}
abstract class Monoid[A] extends SemiGrup[A] {
def zero: A
}
object ImplicitTest extends App {
// mòdul instància de Monoid per al tipus String
implicit object StringMonoid extends Monoid[String] {
def suma(x: String, y: String): String = x concat y
def zero: String = ""
}
// mòdul instància de Monoid per al tipus Int
implicit object IntMonoid extends Monoid[Int] {
def suma(x: Int, y: Int): Int = x + y
def zero: Int = 0
}
// sumaLlista
// el paràm. implícit selecciona instància segons el tipus del paràm. actual (a la crida)
// entre els ''implícit object''s visibles en l'àmbit.
def sumaLlista[A](xs: List[A])(implicit m: Monoid[A]): A =
xs match {
case Nil => m.zero
case y :: ys => m.suma(y, sumaLlista(ys))
}
println(sumaLlista(List(1, 2, 3))) // obté l'objecte implícit segons el tipus del paràmetre.
println(sumaLlista(List(1, 2, 3)) (IntMonoid)) // explicita l'implícit quan n'hi ha diversos (Monoide per a la suma, per al producte, ...)
println(sumaLlista(List("a", "b", "c")))
}
Restricció de context (Context bound) i obtenció d'instàncies amb implicitly
[modifica]Introdueix el requeriment d'instanciació de classes de tipus (genèrics) en el paràmetre de tipus.
La restricció de context [A: Monoid]
indica que cal que hi hagi una instància de Monoid per al tipus A, visible en l'àmbit d'ús.
Codificació alternativa de sumaLlista. La instància de Monoid s'obté amb implicitly.
def sumaLlista[A: Monoid](xs: List[A]): A = {
val m = implicitly[Monoid[A]] // obté una instància de Monoid per al tipus A
xs match {
case Nil => m.zero
case y :: ys => m.suma(y, sumaLlista(ys))
}
}
Restricció de context respecte a Paràmetre implicit - Ordered i Ordering
[modifica]Ordered[A] i Ordering[A] ofereixen una funcionalitat similar
- El tret Ordered[A] equival a la interfície "Ordenat" i implementa la ordenació bàsica del tipus i només se'n defineix una de sola. Es pot especificar com a requeriment de context
def nomFunció [A:Ordered](params...) = ...
- El tret Ordering[A] (Ordenació) equival a una classe de tipus instanciable en implicit objects per tipus, i en podem tenir diverses instàncies per al mateix tipus, amb relacions d'ordre diferents (ascendent, descendent, basada en un component, ...). Per explicitar la que vulguem, s'ha de fer servir com a paràmetre implícit.
def nomFunció [A](params...)(implicit m:Ordering[A]) = ...
Injecció de mètodes
[modifica]La conversió implícita d'un tipus a una classe o tret, té l'efecte d'augmentar els mètodes per al tipus que es converteix.[45]
object Test extends App {
object Augmentador {
class ToMyInt(s:String) {
def toMyInt: Int = java.lang.Integer.parseInt(s)
}
implicit def string2ToMyInt(s: String) = new ToMyInt(s)
}
import Augmentador._ // incorpora l'implícit a l'àmbit
println ("12".toMyInt)
}
- Augment ràpid (en escriure'l però més lent d'execució).[46]
object Test extends App {
implicit def string2ToMyInt(s: String) = new {
def toMyInt: Int = java.lang.Integer.parseInt(s)
}
println ("12".toMyInt)
}
Convenció per indicar efectes col·laterals en els mètodes sense paràmetres
[modifica]- Per convenció, els mètodes sense paràmetres podran ometre els parèntesis, a la definició i en l'ús, per indicar que no inclouen efectes col·laterals.[47]
object Demo {
val valor = ...
def llegir() = ... // efectes col·laterals, cal posar els parèntesis
def obtenirValor = ... // no hi ha efectes col·laterals => no s'hi han de posar els parèntesis
}
Avaluació tardana (ang: lazy)
[modifica]- valors lazy val (retarda l'avaluació fins que fa falta en una expressió)
- amb el qualificador lazy, l'expressió només s'avalua quan fa falta alguna variable del patró, i, a més a més, per l'ús de val memoritza el valor (aprofita el resultat en crides posteriors)
lazy val patró = expressió
- paràmetres per-nom
(param: => tipus)
(Call by name) - afegint
=>
abans del tipus, aquests paràmetres s'avaluen substituint el nom del paràmetre en el codi per l'expressió del paràmetre actual.
object Main extends App {
var cnt = 0
def perNom(n: => Int) = {
println(cnt);
println(n); // aquí se substitueix el nom del param. per l'expressió del param. actual
println(n); // aquí també
}
perNom({ cnt += 1; cnt})
}
execució a l'intèrpret:
scala> Main 0 1 2
- Vistes
- Avaluació no-estricta de les col·leccions.[48]
- Traversable.view
- genera una "vista d'avaluació diferida" d'una col·lecció, resultant un tipus TraversableView. Les transformacions que s'hi apliquin (map, filter, etc.), en comptes de generar una nova estructura, quedaran pendents d'aplicació generant una nova vista, fins que s'hi apliqui el mètode force.[49]
- Traversable.toIterator
- permet recórrer una estructura una sola vegada (l'iterador muda de valor a cada 'next()'). Els elements s'obtenen per necessitat, evitant la generació d'estructures intermèdies en les transformacions successives de l'estil de map, filter, fold.[50]
- Traversable.toStream
- permet obtenir-ne els elements de manera tardana (per necessitat). Els streams es poden recórrer tants cops com calgui a partir d'una referència, per què hi ha memoïtzació (memorització en una taula dels resultats evitant el recàlcul).[51]
evitant la necessitat de desforestació
[modifica]En l'aplicació funcional successiva, ex.: estructura.filtre(predicat).map(funcióDeTransformació).reducció(op_binària), les classes Iterator i Stream, en recuperar els elements de manera tardana, eviten la problemàtica de la desforestació (malversació d'espai per la creació d'estructures intermèdies temporals quan l'avaluació és estricta) i la necessitat d'escriure rutines multifunció filtreMapReducció(predicat, funcióDeTransformació, op_binària) d'un sol bucle per solucionar-ho.
Intervals numèrics
[modifica]La classe Range permet definir iteradors (avaluació tardana) sobre seqüències numèriques de tipus Int.
La classe NumericRange[T] generalitza la classe Range a sencers de diferents precisions (Han d'implementar Integral[T] (anell unitari íntegre ordenat euclidià: ops. dels enters).
(0 to 2).toList // interval tancat [0,2]: List(0,1,2)
(0 until 2).toList // interval obert per la dreta [0,2): List(0,1)
(2 to 0 by -1).toList // interval amb increment: List(2,1,0)
Scala té una llarga biblioteca per facilitar la feina amb varietat de col·leccions immutables i mudables així com tractament de XML i altres que trobareu aquí.[52]
L'especificació oficial del llenguatge és aquí.[53]
Els elements predefinits són a l'object Predef[54]
Cercadors de l'API
[modifica]La API de Scala www.scala-lang.org/api/current/ permet cercar per nom de paquet, o signatures d'àmbit global.
- Hi ha un cercador d'índex de biblioteques a la mateixa web anomenat "Scaladex": index.scala-lang.org
- La web Scalex.org Arxivat 2020-11-11 a Wayback Machine. permetia, fins fa poc, cercar els identificadors exportats per un paquet, de manera similar a Hayoo[55] o bé Hoogle[56] per al llenguatge Haskell. Tanmateix se'n pot descarregar el codi del Github.[57]
- Des del Setembre/2015 hi ha un nou cercador anomenat "Scaps" a scala-search.org Arxivat 2016-06-10 a Wayback Machine..[58]
Biblioteques
[modifica]Alguns paquets que abans estaven inclosos a la biblioteca bàsica, com ara scala.xml, ara s'han de referenciar separadament des del GitHub de Scala.
La llista de biblioteques de tercers és a Scala wiki - Tools and Libraries.
Scala Actors -- Concurrència amb el model d'Actors
[modifica]Vegeu ref.[59]
STM (Transaccions de memòria per programari)
[modifica]Col·leccions
[modifica]Vegeu ref.[62]
Gràfic de les col·leccions immutables.
Gràfic de les col·leccions mudables.
- paral·lelisme
- Scala també disposa de col·leccions per al paral·lelisme, per aprofitar la potència dels processadors multicor amb algorismes que utilitzen l'estratègia "dividir i vèncer".[63] El mètode par ofereix implementacions amb paral·lelisme de la majoria de col·leccions que implementen el tret ParIterable[64] amb l'ajut d'un particionador (Splitter) que ofereix un iterador sobre les subcol·leccions disjuntes resultants de la partició.[65]
(col·lecció.par.map (_ * 2)).toList // multiplica, paral·lelitzant, els elements d'una col·lecció de numèrics [http://www.scala-lang.org/api/current/index.html#scala.math.Numeric]
// el factor 2 és convertit al tipus de l'element si existeix una conversió implícita
- concurrència
- Existeix una versió de diccionaris TrieMap (basats en arbres de prefix i taula de dispersió (ang:hash-array)) per a l'accés concurrent (simultani des de diversos fils d'execució) i lliure de baldes (ang:lock-free).[66]
operacions comunes dels mòduls estàtics (object) que acompanyen les col·leccions
[modifica]Vegeu "Creating collections".[67]
// Generador de col·lecció buida
def empty[A]: Col·lecció[A]
// Generador partint d'elements oferts en una crida de nombre variable de paràmetres
def apply[A](xs: A*): Col·lecció[A]
// el nom apply es pot ometre, és el nom de funció per defecte
// Col·lecció(elem1, elem2, ...) equival a Col·lecció.apply(elem1, elem2, ...)
operacions comunes de les classes de les col·leccions
[modifica]def isEmpty: Boolean
def nonEmpty: Boolean
// companion: 'Object' fàbrica d'instàncies de la col·lecció (generadors i altres mètodes estàtics)
def companion: GenericCompanion[Col·lecció]
def sameElements(that: GenIterable[A]): Boolean // compara els elements amb la col·lecció 'that'
def iterator: Iterator[A] // obté un iterador (mètodes: hasNext(), next()) sobre els elements
def mkString: String // mostra la col·lecció en una String
def mkString (sep: String): String // ... intercalant separador
def toString(): String // a String
def toArray: Array[A] // a Array, correspon als vectors del Java del tipus (A [])
def toBuffer[B >: A]: Buffer[B] // a Buffer (magatzematge temporal incrementable)
def toSeq: Seq[A] // a seqüència, implementada amb List
def toSet[B >: A]: Set[B] // a conjunt
def toList: List[A] // a llista
def toIndexedSeq: IndexedSeq[A] // a seqüència d'accés aleatori, implementada amb Vector
def toVector: scala.Vector[A] // a vector, implementació per defecte de les IndexedSeq
def toMap[T, U]: collection.Map[T, U] // a diccionari, si i només si els elements són parells (A ~ (T, U))
def toIterator: Iterator[A] // obté un iterador (travessable un sol cop), equival al mètode 'iterator'
def toStream: Stream[A] // obté un Stream (travessable més d'un cop)
def toTraversable: collection.Traversable[A] // a Travessable (mètode forEach) // retorna la mateixa instància si la col·lecció ja ho és.
def to[Col[_]]: Col[A] // a col·lecció explicitant el paràmetre de tipus
scala> (1 to 3).to[List]
res1: List[Int] = List(1, 2, 3)
scala> (1 to 3).to : List[Int] // o bé mitjançant una restricció de tipus
res2: List[Int] = List(1, 2, 3)
- Seqüències[68] (entre d'altres):
// apply: obtenir element enèsim
// el nom apply es pot ometre, és el nom de funció per defecte:
// l'element de la col·lecció xs escrit xs(n) equival a xs.apply(n)
def apply(idx: Int): A // obtenir l'element a l'índex 'idx'
def isDefinedAt(idx: Int): Boolean // està definit per a l'índex 'idx' ?
def reverseIterator: Iterator[A] // iterador en sentit invers
- IndexedSeq: seqüències d'accés aleatori: ArrayBuffer, Vector, ...
- LinearSeq: seqüències d'accés lineal (d'un amb un)
- avaluació estricta: List, Queue
- avaluació tardana: Iterator, Stream
- avaluació diferida: TraversableView (difereix l'avaluació dels mètodes aplicats, fins que s'aplica .force o se'n demana la conversió a una col·lecció concreta)[69]
- Altres signatures: Traversable, Iterable, Set, Map
Interfície TraversableOnce
[modifica]Vegeu ref.[70]
- interfície per les operacions de Reducció d'una col·lecció a un valor (gramaticalment: A* ⇒ A)
// A és el tipus de l'element de la col·lecció
def foldLeft[B](z: B)(op: (B, A) ⇒ B): B // plegat per l'esquerra sobre valor inicial
def foldRight[B](z: B)(op: (A, B) ⇒ B): B // plegat per la dreta sobre valor inicial
// [A1 >: A] vol dir que els paràmetres es tipifiquen per algun tipus A1 supertipus de A
// si l'operació del plegat és associativa en el domini dels elements,
// tindrem el mateix resultat per l'esquerra com per la dreta, per tant no cal explicitar-ho.
def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1 // plegat endomòrfic sobre valor inicial amb op. associativa
// (z /: xs) equival a (xs foldLeft z) operador associatiu per la dreta (xq. acaba en ':')
def /:[B](z: B)(op: (B, A) ⇒ B): B
// (xs :\ z) equival a (xs foldRight z) op. associatiu per l'esquerra.
def :\[B](z: B)(op: (A, B) ⇒ B): B
def reduceLeft[B >: A](op: (B, A) ⇒ B): B // plegat per l'esquerra de la cua sobre el cap (col·l. no buida); func. parcial !!
def reduceLeftOption[B >: A](op: (B, A) ⇒ B): Option[B] // versió total (col·l. buida => None)
def reduce[A1 >: A](op: (A1, A1) ⇒ A1): A1 // plegat endomòrfic de la cua sobre el cap, amb op. associativa (col·l. no buida); func. parcial !!
def reduceOption[A1 >: A](op: (A1, A1) ⇒ A1): Option[A1] // versió total (col·l. buida => None)
- plegats sobre domini que implementi Numèric, math.Numeric és la signatura d'un Anell unitari ordenat [71]
- plegats de col·leccions d'elements ordenats, o via projecció a imatge ordenable.[72]
def sum[A: Numeric]: A
def product[A: Numeric]: A
// plegats; el tipus de l'elem ha d'ésser subtipus d'algun que implementi Ordering (Ordenació)
// quina instància d'ordenació (ord. ascendent, descendent, s/. l'ordre d'un component) es pot adjuntar com a param.
def min[B >: A](implicit cmp: Ordering[B]): A
def max[B >: A](implicit cmp: Ordering[B]): A
// plegats via funció de projecció amb imatge ordenable quina instància d'ordenació es pot adjuntar com a param.
def minBy[B](f: (A) ⇒ B)(implicit cmp: Ordering[B]): A
def maxBy[B](f: (A) ⇒ B)(implicit cmp: Ordering[B]): A
Interfície Traversable
[modifica]Interfície TraversableLike[73] per al recorregut (visita dels elements) d'una col·lecció; defineix foreach i conté la major part dels mètodes de Traversable, afegeix a TraversableOnce, a banda d'altres trets, els mètodes següents
- Consulta
// A és el tipus de l'element de la col·lecció de tipus Repr
def isEmpty: Boolean // és buida ?
def nonEmpty: Boolean // és no buida ?
def size: Int // mida
def hasDefiniteSize: Boolean // és finita la col·lecció ?
def isTraversableAgain: Boolean // és travessable altra vegada? (els iteradors només una)
// com a seqüència
def head: A // cap, funció parcial!!! excepció NoSuchElementException si col. buida
def headOption: Option[A] // cap, versió total
def tail: Repr // cua, funció parcial!!! excepció NoSuchElementException si col. buida
// quantificadors amb predicat
def exists (predicat: (A) ⇒ Boolean): Boolean // algun element compleix el predicat?
def forall (predicat: (A) ⇒ Boolean): Boolean // el compleixen tots?
def count (predicat: (A) ⇒ Boolean): Int // compta els que compleixen
// cerca per predicat
def find (predicat: (A) ⇒ Boolean): Option[A] // cerca per predicat
// altres components
def last: A // darrer. funció parcial!!! excepció NoSuchElementException si col. buida
def lastOption: Option[A] // darrer, versió total
def init: Repr // descarta el darrer. funció parcial!!! excepció UnsupportedOperationException si col. buida
// tails: successió de subcol·leccions escapçant pel davant
def tails: Iterator[Repr] // equival a ((0 to col.size).map (col.drop))
// inits: successió de subcol·leccions escapçant pel darrere
def inits: Iterator[Repr] // equival a ((col.size to 0 by -1).map (col.take))
- Combina
def ++ [B](that: GenTraversableOnce[B]): Traversable[B] // (xs ++ that) concatena associatiu per l'esquerra
// els operadors acabats en ':' (associatius per la dreta) en notació infix, són mètodes de l'operand de la dreta
def ++: [B](that: TraversableOnce[B]): Traversable[B] // (that ++: xs) concatena associatiu per la dreta
- Visita i/o transforma
def foreach [U](f: Elem => U): Unit // iteració amb funció d'efectes laterals
def map [B](f: (A) ⇒ B): Traversable[B] // mapeja (obté la col·lecció de les imatges de l'aplicació f)
// encadena amb funció de resultat múltiple (col·lecció) i concatena resultats
def flatMap [B](f: (A) ⇒ GenTraversableOnce[B]): Traversable[B] // equival a l'encadenament monàdic del llenguatge Haskell (>>=)
// collect i collectFirst ofereixen un mapeig amb funcions que implementen el tret PartialFunction,
// retornant la col·lecció de les imatges corresponents als elements per als quals la funció `pf` està definida.
def collect [B](pf: PartialFunction[A, B]): Traversable[B]
def collectFirst [B](pf: PartialFunction[A, B]): Option[B] // obté, la primera de les imatges dels elements vàlids en el subdomini de 'pf'
- Partició
def slice (from: Int, until: Int): Repr // subcol·lecció corresp. a l'interval obert per la dreta [from .. until) (exclou límit superior)
// a l'índex
def take (n: Int): Repr // prefix de n elements
def drop (n: Int): Repr // escapça el prefix de n elements
def splitAt (n: Int): (Repr, Repr) // partició a l'índex, com {col => (col.take(n), col.drop(n))}
// segons predicat
def filter (predicat: (A) ⇒ Boolean): Repr // subcol·lecció dels que compleixen
def partition (predicat: (A) ⇒ Boolean): (Repr, Repr) // parteix com {col => (col.filter (predicat), col.filter (not predicat))}
def takeWhile (predicat: (A) ⇒ Boolean): Repr // prefix mentre elements compleixen
def dropWhile (predicat: (A) ⇒ Boolean): Repr // sufix de takeWhile(predicat)
def span (predicat: (A) ⇒ Boolean): (Repr, Repr) // parteix com {col => (col.takeWhile(predicat), col.dropWhile(predicat))}
// agrupa els elements en funció de les imatges de 'f', en un diccionari de subcol·leccions
def groupBy [K](f: (A) ⇒ K): immutable.Map[K, Repr]
// monàdic, per a ser emprat a les clàusules 'for' i llistes per comprensió (encadenament monàdic)
def withFilter (predicat: (A) ⇒ Boolean): FilterMonadic[A, Repr] // filtre monàdic
// TraversableView difereix les transformacions posteriors fins que s'aplica '.force' o es converteix a col·lecció concreta.
def view: TraversableView[A, Repr] // vista d'avaluació diferida
def view (from: Int, until: Int): TraversableView[A, Repr] // llesca d'avaluació diferida
- Reducció
// successió de plegats parcials per l'esquerra
def scanLeft [B, That](z: B)(op: (B, A) ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That
// successió de plegats parcials per la dreta
def scanRight [B, That](z: B)(op: (A, B) ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That
// scan: successió de plegats parcials amb op. associativa (tant és si el plegat és per la dreta o per l'esquerra)
def scan [B >: A, That](z: B)(op: (B, B) ⇒ B)(implicit cbf: CanBuildFrom[Repr, B, That]): That
// concatena elements de col·lecció de col·leccions travessables
def flatten [B](implicit asTraversable: (A) ⇒ GenTraversableOnce[B]): Traversable[B]
El tret CanBuildFrom
[modifica]El tret CanBuildFrom[-ColOrigen, -Elem, +ColDestí]
descriu una interfície genèrica per la generació de col·leccions des d'altres col·leccions amb implementacions optimitzades per a tipus específics quines instàncies (objectes implícits) han d'estar visibles.[74]
És d'interés quan, com a sortida d'un mètode d'una col·lecció, es vol crear una col·lecció diferent. Per això només caldrà especificar, amb una restricció del tipus resultant, la col·lecció de destinació.
Interficie Iterable
[modifica]IterableLike, caracteritzada per definir un iterador que faciliti visitar els elements, i que conté la major part dels mètodes d'Iterable[75] afegeix a TraversableLike entre d'altres els següents:
- partició:
def takeRight(n: Int): Repr // obtenir sufix de llargada n
def dropRight(n: Int): Repr // escapçar el sufix
def grouped(n: Int): Iterator[Repr] // retorna iterador de trams de llargada fixa n
- combinació:
def zip[B](that: GenIterable[B]): GenIterable[(A, B)] // aparellament per posició amb altra col·lecció
// zipAll: zip igualant llargades amb farciment
def zipAll[B](that: collection.Iterable[B], thisElem: A, thatElem: B): GenIterable[(A, B)]
def zipWithIndex: GenIterable[(A, Int)] // aparella una seqüència amb l'índex correlatiu (partint de zero)
mètodes de consulta específics
[modifica]// a les seqüències
def apply (n: Int): A // obtenir elem. enèsim partint de zero: xs(3). funció parcial!!! amb IndexOutOfBoundsException
// comprova amb una segona seqüència si es compleix un predicat entre els corresponents posicionals
def corresponds [B](that: GenSeq[B])(predicat: (A, B) ⇒ Boolean): Boolean
// als conjunts
def contains (key: A): Boolean
def subsetOf (that: GenSet[A]): Boolean // és subcjt de ?
def subsets (len: Int): Iterator[Set[A]] // subcjts de llarg. específica
def subsets: Iterator[Set[A]] // tots els subconjunts
// als diccionaris
def get (key: A): Option[B]
// als opcionals ''Option[+A]''
def getOrElse[B >: A](default: ⇒ B): B // cas de la variant (Some x) n'extreu el valor, altrament avalua el param.
def toRight[T](left: ⇒ T):Either[T, A] // cas de la variant (Some x) torna Right(x), altrament Left amb el param.
def toLeft[T](right: ⇒ T):Either[A, T] // cas de la variant (Some x) torna Left(x), altrament Right amb el param.
// al dual ''Either[+A, +B](Left(l:A) | Right(r:B))''
left.getOrElse [AA >: A](or: ⇒ AA): AA // cas de la variant (Left a) n'extreu el valor, altrament avalua param.
right.getOrElse [BB >: B](or: ⇒ BB): BB // cas de la variant (Right b) n'extreu el valor, altrament avalua param.
left.toOption: Option[A] // cas de Left(a) torna Some(a), altrament None
right.toOption: Option[B] // cas de Right(b) torna Some(b), altrament None
// al dual ''Try[+T] d'avaluació d'expressions que poden llançar excepcions
// avaluant a Success(resultat:T) | Failure(excepció: Throwable)''
def get: T // cas de la variant (Success resultat) retorna resultat, cas de (Failure excep) llança l'excepció
def getOrElse [U >: T](default: ⇒ U): U // cas de la variant (Success r) n'extreu el valor, altrament avalua el param.
def toOption: Option[T] // cas de Success(resultat) retorna Some(resultat) altrament None
- seqüències com a Multiconjunt
def contains(elem: Any): Boolean // està contingut?
def distinct: Seq[A] // diferents (elimina elements repetits)
def diff (that: Seq[A]): Seq[A] // multi-diferència, per cada elem. del de la dreta, elimina una ocurrència.
def union (that: Seq[A]): Seq[A] // multi-unió (concatena)
def intersect (that: Seq[A]): Seq[A] // multi-intersecció
def combinations (n: Int): Iterator[Seq[A]]
def permutations: Iterator[Seq[A]]
- seqüències, accés indexat [0..
def indices: immutable.Range // retorna rang d'índexs
// obtenir element per posició (basada en 0), ''apply'' és el nom de funció per defecte i es pot ometre: xs(n)
def apply (n: Int): A // Funció parcial!!! IndexOutOfBoundsException
// cerca posició per valor
def indexOf (elem: A [, from: Int]): Int // retorna índex o bé (-1)
def lastIndexOf (elem: A [, end: Int]): Int
// cerca posició per predicat
def indexWhere (predicat: (A) ⇒ Boolean [, from: Int]): Int
def lastIndexWhere (predicat: (A) ⇒ Boolean [, end: Int]): Int
// actualització funcional amb associació (índex, elem)
def updated [A1 >: A](index: Int, elem: A1): Repr // retorna nova col·lecció actualitzada
- seqüències, consulta de subseqüències
def slice (from: Int, until: Int):Seq[A] // subseq. a l'interval obert per la dreta
def containsSlice [B](that: GenSeq[B]): Boolean // conté subseq. ?
def startsWith [B](that: GenSeq[B], offset: Int): Boolean // comprova prefix
def endsWith [B](that: GenSeq[B]): Boolean // comprova sufix
// cerca posició de subseq.
def indexOfSlice [B >: A](that: GenSeq[B] [, from: Int]): Int // retorna pos. o bé (-1)
def lastIndexOfSlice [B >: A](that: GenSeq[B] [, end: Int]): Int // retorna pos. o bé (-1)
// llarg. de subseq. dels elements que compleixen el predicat
def prefixLength (predicat: (A) ⇒ Boolean): Int // llargada de `takeWhile(predicat)`
def segmentLength (predicat: (A) ⇒ Boolean, from: Int): Int // llargada `drop(from).takeWhile(predicat)`
// obtenció de subseqüències desplaçant una finestra de la mida esmentada
def sliding (size: Int): Iterator[Seq[A]]
def sliding (size: Int, step: Int): Iterator[Seq[A]] // step: especifica la distància entre inicis
- seqüències, ordenació
def sortWith (lt: (A, A) ⇒ Boolean): Seq[A] // especificant la relació d'ordre
// sorted: si l'element és subtipus d'un ordenable, quina instància d'ordenació podem especificar
def sorted [B >: A, B](implicit cmp: Ordering[B]): Seq[A]
// ordena segons la imatge ordenable d'una projecció quina instància d'ordenació podem especificar
def sortBy [B](f: (A) ⇒ B)(implicit cmp: Ordering[B]): Seq[A]
operadors d'actualització
[modifica]Els operadors binaris en notació infix i associatius per la dreta (aquells quin símbol acaba en ':') s'avaluen sintàcticament com a mètodes de l'operand de la dreta. (exemple: x1 +: (x2 +: xs)
; la col·lecció va a la dreta quan l'operador acaba en ':')
Cas d'operacions amb dues versions funcional i imperativa, s'utilitza el mode verbal infinitiu per la versió imperativa i el mode verbal participi per la versió funcional. Per exemple a les seqüències d'accés aleatori (IndexedSeq[A]) update és la versió imperativa i updated la versió funcional.
a les seqüències
[modifica]per a les seqüències dels tipus immutable.Seq[+A]
i mutable.Seq[A]
.
funcionals purs a les seqüències
[modifica]- Operadors i mètodes funcionals purs (sense efectes col·laterals) de les seqüències (immutables o mudables)
- retornen nova seqüència
x +: xs // nova seq. afegint al capdavant (associatiu per la dreta xq. acaba en ':')
xs :+ x // nova seq. afegint pel final
xs ++ (ys: GenTraversableOnce[A]) // nova seq. concatenant amb elements de col·lecció
(ys: TraversableOnce[A]) ++: xs // nova seq. prefixant elems. de col·lecció (op. associatiu per la dreta)
// updated: actualització funcional que retorna nova seq. actualitzada
// cas de col·lecció covariant en el tipus de l'elem Col[+A], l'argument (posició contravariant)
// ha d'admetre tipus més generals: A1 tals que (A1 >: A).
// altrament A1 = A
def updated [A1 >: A](index: Int, elem: A1): Repr // actualitza amb elem. a l'índex
// patch (apedaçar): retorna còpia inserint seqüència ''that'' a l'índex ''from'', substituint ''replaced'' elements
def patch (from: Int, that: GenSeq[A], replaced: Int): Repr
// combina com a multi-conjunt
xs intersect ys // multi-intersecció
xs diff ys // multi-diferència
xs union ys // multi-unió (concatenació)
def distinct: Seq[A] // distints (nova seq. eliminant repetits)
// transposició d'una seqüència de seqüències
def transpose [B](implicit asTraversable: (A) ⇒ GenTraversableOnce[B]): Seq[Seq[B]]
mutadors a les seqüències
[modifica]- Operadors mutadors de les seqüències mudables (
ListBuffer[A]
iMutableList[A]
)
x +=: xs // afegir pel davant (associatiu per la dreta)
xs += x // afegir al final
xs -= x // eliminar
xs.+= (x, y, ...) // afegir diversos (llista d'arguments variable)
xs.-= (x, y, ...) // eliminar diversos
xs ++= (ys: TraversableOnce[A]) // afegir elems. de col·lecció travessable
xs --= (ys: TraversableOnce[A]) // eliminar elems de col·lecció travessable
def transform(f: (A) ⇒ A): Seq.this.type // aplica una funció als elements de la seqüència
// només a ListBuffer
(ys: TraversableOnce[A]) ++=: xs // afegeix al capdavant elements de col·lecció
als Buffer
[modifica]Buffer: estructura mudable de magatzematge temporal incrementable que permet eliminacions.
Per al tipus mutable.Buffer[A]
.[76]
- Mètodes mutadors als Buffers, addicionals als esmentats prèviament per a les seqüències
def append (elems: A*): Unit // afegeix diversos (llista d'arguments variable)
def appendAll (ys: TraversableOnce[A]): Unit // afegeix de col·lecció
def prepend (elems: A*): Unit // prefixa diversos
def prependAll (ys: TraversableOnce[A]): Unit // prefixa de col·lecció
def insert (n: Int, elems: A*): Unit // insereix diversos a la posició n
def insertAll (n: Int, ys: Traversable[A]): Unit // insereix de col·lecció a la pos. n
def update (n: Int, newelem: A): Unit // actualitza l'enèsim
def remove (n: Int): A // elimina l'enèsim
def trimStart (n: Int): Unit // retalla pel davant n elems.
def trimEnd (n: Int): Unit // retalla pel darrere n elems.
def clear(): Unit // buida
a les seqüències d'accés aleatori, anomenades indexades
[modifica]per a seqüències d'accés aleatori dels tipus immutable.IndexedSeq[+A]
i mutable.IndexedSeq[A]
- Mètodes funcionals a les seq. indexades IndexedSeq (mudables o immutables)
updated (mode verbal participi) és la versió funcional del mutador update (verb en mode imperatiu), descrites tot seguit:
// updated: actualització funcional que retorna una nova seq. indexada amb l'actualització
// cas de col·lecció covariant a l'elem. (+A), requereix que el tipus de l'elem. a l'argument (posició contravariant)
// ha d'admetre tipus més generals: A1 tals que (A1 >: A).
// altrament A1 = A
def updated[A1 >: A](índex: Int, elem: A1): IndexedSeq[A] // retorna nova seq. d'accés aleatori
- Mètodes mutadors a les seq. indexades mudables mutable.IndexedSeq
def update(índex: Int, elem: A): Unit // actualitza la posició indexada
a les cues
[modifica]per a les cues dels tipus immutable.Queue[+A]
i mutable.Queue[A]
.
funcionals purs a les cues
[modifica]- Operadors i mètodes funcionals purs (sense efectes col·laterals) de les cues (immutables o mudables)
- retornen nova cua.
xs :+ x // nova cua, encuant al final
x +: xs // nova cua, encuant al capdavant (associatiu per la dreta xq. acaba en ':')
xs ++ (ys: GenTraversableOnce[A]) // nova cua encuant elements de col·lecció
(ys: TraversableOnce[A]) ++: xs // nova cua prefixant elems. de col·lecció (op. associatiu per la dreta)
- Operadors i mètodes funcionals purs (sense efectes col·laterals) només de les cues immutables.
def enqueue[B >: A](elem: B): Queue[B] // encua un element retornant nova cua
def enqueue[B >: A](iter: Iterable[B]): Queue[B] // encua els elements d'una col·lecció iterable.
def dequeue: (A, Queue[A]) // descompon la cua NO BUIDA en parell (cap, resta) // funció parcial !!
def dequeueOption: Option[ (A, Queue[A]) ] // versió total
mutadors a les cues
[modifica]- Operadors mutadors de les cues mudables (
mutable.Queue[A]
)
xs += x // encua al final
x +=: xs // encua al capdavant (associatiu per la dreta)
xs.+= (x, y, ...) // encua diversos (llista d'arguments variable)
xs ++= (ys: TraversableOnce[A]) // encua elems. de col·lecció travessable
def enqueue(elems: A*): Unit // encua diversos (llista d'arguments variable)
def dequeue: A // desencua el cap i el retorna.
// segons predicat.
def dequeueFirst(predicat: (A) ⇒ Boolean): Option[A] // desencua el primer que compleix el predicat
cues amb prioritat
[modifica]Cues amb prioritat definida per un ordre implícit o bé explícit. mutable.PriorityQueue[A]
Els elements s'encuen davant d'aquells que són menors en l'ordre.
Integral[T][77] especifica un anell íntegre que implementa la divisió euclidiana, habitualment enters de qualsevol precisió.
scala> import scala.collection.mutable.{PriorityQueue => PQ}
import scala.collection.mutable.{PriorityQueue=>PQ}
// amb ordre implícit, prioritat al més gran
scala> PQ(3,5,2).head
res2: Int = 5
scala> val intOps = implicitly[Integral[Int]] -- Integral[T]: ops. dels íntegres (anell euclidià)
intOps: Integral[Int] = scala.math.Numeric$IntIsIntegral$@3a20f22d
// ordenació explícita per establir prioritat al més petit
scala> PQ(3,5,2)(intOps.reverse).head
res8: Int = 2
- Operadors mutadors de les mudables "cues amb prioritat" (
mutable.PriorityQueue[A]
)
xs += x // encua un elem.
xs.+= (x, y, ...) // encua diversos (llista d'arguments variable)
xs ++= (ys: TraversableOnce[A]) // encua elems. de col·lecció travessable
def enqueue(elems: A*): Unit // encua diversos (llista d'arguments variable)
def dequeue: A // desencua el cap i el retorna.
També es pot definir l'element com a parell (prioritat, valor) definint l'ordre per comparació del primer de la tupla. Vegeu exemple.
als conjunts
[modifica]per a conjunts del tipus immutable.Set[A]
i mutable.Set[A]
.[78]
funcionals purs als conjunts
[modifica]- Operadors funcionals purs (sense efectes col·laterals) dels conjunts (Set) (immutables o mudables)
- retornen nou conjunt
xs + x // actualitza (unió) amb {x}
xs - x // diferència amb {x}
xs.+ (x, y, ...) // actualitza amb diversos elements (llista d'arguments variable)
xs.- (x, y, ...) // diferència amb diversos
xs ++ (ys: GenTraversableOnce[A]) // actualitza amb elements de col·lecció
xs -- (ys: GenTraversableOnce[A]) // diferència conjunt col·lecció
(ys: TraversableOnce[A]) ++: xs // actualització, associatiu per la dreta
xs & ys; xs intersect ys // intersecció de conjunts
xs &~ ys; xs diff ys // diferència de conjunts
xs | ys; xs union ys // unió de conjunts
mutadors als conjunts
[modifica]- Operadors mutadors dels conjunts mudables
xs += x // inserir
xs -= x // eliminar
xs.+= (x, y, ...) // inserir diversos (llista d'arguments variable)
xs.-= (x, y, ...) // eliminar diversos
xs ++= (ys: Traversable[A]) // inserir elems de col·lecció
xs --= (ys: Traversable[A]) // elimina elems de col·lecció
als diccionaris
[modifica]per a diccionaris (taules associatives) amb tipus immutable.Map[A, +B]
i mutable.Map[A, B]
.[79]
- associació és una denominació de la correspondència (clau, valor)
funcionals purs als diccionaris
[modifica]- Operadors funcionals purs (sense efectes col·laterals) dels diccionaris (Map) (immutables o mudables)
- retornen nou diccionari
dicc + ((k, v)) // unió de dicc. amb parell (clau, valor)
dicc - k // diferència amb el parell de la clau k
dicc.+ ((k1, v1), (k2, v2), ...) // unió amb diversos parells (llista d'arguments variable)
dicc.- (k1, k2, ...) // diferència amb els parells quines claus s'esmenten
dicc ++ (ys: GenTraversableOnce[(A, B)]) // unió amb parells de col·lecció
dicc -- (ys: GenTraversableOnce[A]) // diferència amb claus de col·lecció
(ys: TraversableOnce[(A, B)]) ++: dicc // unió amb parells de col·lecció, associatiu per la dreta
// updated: actualització funcional que retorna nou diccionari actualitzat
// cas de col·lecció covariant a l'elem.(+B), requereix que el tipus de l'elem. a l'argument (posició contravariant)
// ha d'admetre tipus més generals: B1 tals que (B1 >: B).
// altrament B1 = B
dicc.updated[B1 >: B](key: A, value: B1): Map[A, B1] // unió amb associació (k, v)
mutadors als diccionaris
[modifica]- Operadors mutadors dels diccionaris mudables
dicc += ((k, v)) // afegeix associació de parell (clau, valor)
dicc -= k // elimina associació de clau k
dicc.+= ((k1, v1), (k2, v2), ...) // incorpora diversos parells (llista d'arguments variable)
dicc.-= (k1, k2, ...) // elimina diverses claus
dicc ++= (ys: GenTraversableOnce[(A, B)]) // incorpora associacions de col·lecció de parells
dicc --= (ys: GenTraversableOnce[A]) // elimina claus de la col·lecció
dicc.update(key: A, value: B): Unit
Cadenes de text, Plantilles i Expressions regulars
[modifica]String com a seqüència
[modifica]El tipus String és augmentat implícitament a StringOps[80] permetent aplicar-hi la interfície IndexedSeqLike[81] (tractant String com una seq. d'accés aleatori). A més a més, incorpora els mètodes següents:
def lines: Iterator[String] // parteix en línies
def linesWithSeparators: Iterator[String] // parteix ... incorporant separadors de línia, siguin {LF, CRLF, FF}
def r: Regex // interpreta la cadena com a patró d'expressió regular, obtenint-ne un valor Regex
def r (groupNames: String*): Regex // ... donant noms als grups
scala> """(\d\d)-(\d\d)-(\d\d\d\d)""".r("mes", "dia", "any").findFirstMatchIn("12-31-1999").map(_.group("dia"))
res0: Option[String] = Some(31)
def capitalize: String // retorna String amb majúscula al primer caràcter
// to{tipus}: interpreta contingut com a literal del tipus, llançant excepció si no concorda en tota l'extensió.
def {toBoolean, toByte, toShort, toInt, toLong, toFloat, toDouble}
// format: utilitza el contingut de la cadena com a format per mostrar args, igual que 'sprintf' de C
def format(args: Any*): String
def padTo(len: Int, elem: A): String[A] // farceix al final fins a llargada 'len' amb 'elem'
def stripPrefix (prefix: String): String // escapça el prefix
def stripSuffix (suffix: String): String // escapça el sufix
// 'stripMargin' escapça marge esquerre (blancs i tabuladors) fins a un delimitador de marge,
// en una String que pot ser multilínia (literals amb delimitador triple),
// només d'aquelles línies de la cadena que continguin el delimitador!!
def stripMargin: String // escapça el marge esquerre delimitat per '|', incloent el delimitador
def stripMargin (delimitadorDeMarge: Char): String // igual per al delimitador especificat.
def stripLineEnd: String // elimina {LF, CRLF, FF} del final
// diversos mètodes de java.lang.CharSequence
def charAt(arg0: Int): Char
def length(): Int
def subSequence(arg0: Int, arg1: Int): CharSequence
// diversos mètodes de java.lang.String
def matches(arg0: String): Boolean
def regionMatches(arg0: Boolean, arg1: Int, arg2: String, arg3: Int, arg4: Int): Boolean
def replaceFirst(arg0: String, arg1: String): String
def replace(arg0: Char, arg1: Char): String
def replace(arg0: CharSequence, arg1: CharSequence): String
def replaceAll(arg0: String, arg1: String): String
def substring (arg0: Int): String
def substring (arg0: Int, arg1: Int): String
def trim(): String
def toCharArray(): Array[Char]
def to{Lower|Upper}Case (): String // Minúscules/Maj.
def to{Lower|Upper}Case (arg: java.util.Locale): String // Min./Maj. segons 'Locale'
...
String especial per a multi-línia, i expressions regulars
[modifica]També anomenat "document tot seguit" (ang:heredoc), amb cometes triples. La barra '\' invertida no s'hi interpreta com a caràcter d'escapament, permetent escriure-hi expressions regulars que no les han de doblar.
""" Primera línia
continuació"""
""" Primera línia, "entrecomillat"
|continuació amb marge""".stripMargin // stripMargin elimina el marge esquerre fins al delimitador '|'
""" Si no volem el delimitador de marge '|'
#continuació amb marge i delimitador especial""".stripMargin('#') // ... per al delimitador de marge especificat
// per al cas d'expressions regulars
"""(\d\d\d\d)-(\d\d)-(\d\d)""" // Aquí la barra invertida NO s'interpreta com a caràcter d'escapament
"tira normal\n" // Aquí la barra invertida sí que s'interpreta com a caràcter d'escapament
Plantilles
[modifica]Plantilles amb interpolació, identificades per un prefix de plantilla.[82]
// La plantilla "s" com a: s"Hola, $nom !!"
val nom = "Jaumet"
println(s"Hola $nom !!") // Hola Jaumet !!
// si l'expressió a interpolar és més complexa, cal delimitar-la entre claus
scala> val benvinguda = "benvingut !!"
scala> s"Hola $nom. ${benvinguda.capitalize}"
res1: String = Hola Jaumet. Benvingut !!
// La plantilla "f" permet afegir-hi especificadors de format com els del "printf" del llenguatge C
scala> val edat = 10
scala> f"En $nom%15s té $edat%3d anys !!"
res2: String = En Jaumet té 10 anys !!
Expressions regulars
[modifica]- El tipus Regex.[83]
A l'avaluador d'expressions:
scala> val regexDataISO = """(\d\d\d\d)-(\d\d)-(\d\d)""".r // expr. de tipus Regex
regexData: scala.util.matching.Regex = (\d\d\d\d)-(\d\d)-(\d\d)
scala> "2004-01-20" match {case regexDataISO (any, mes, dia) => s"allò va passar el $dia/$mes/$any."; case _ => "no s'ha trobat cap data!!"}
res4: String = allò va passar el 20/01/2004.
// per al cas de voler caçar múltiples ocurrències
scala> (for (m <- regexDataISO findAllMatchIn "2004-03-01, 2005-12-02") yield { m.subgroups match {case List(any, mes, dia) => s"$dia/$mes/$any"}}).toList
res5: List[String] = List(01/03/2004, 02/12/2005)
Biblioteques rellevants
[modifica]- scala-arm
- [39] control d'excepcions en la gestió de recursos amb alliberament automàtic en acabar. Evita, en cas d'adquisició de múltiples recursos, la implantació de construccions try-finally una dins l'altra.
import resource._
// dos recursos, còpia de input a output, amb alliberament automàtic
for(input <- managed(new java.io.FileInputStream("entrada.txt");
output <- managed(new java.io.FileOutputStream("sortida.txt")) {
val buffer = new Array[Byte](512)
def read(): Unit = input.read(buffer) match {
case -1 => ()
case n => output.write(buffer,0,n); read()
}
read()
}
- scalaz
- [84][85] biblioteca que incorpora TADs del llenguatge Haskell (Monoides, Functors, Functors aplicatius, Mònades, Fletxes, ...)
- slick
- [86] aporta col·leccions específiques per a l'accés a BDD relacionals generant SQL a partir de mètodes funcionals.
val explicitInnerJoin = for {
(c, s) <- coffees join suppliers on (_.supID === _.id) // FROM coffees AS c INNER JOIN suppliers AS s ON c.supid = s.id
if c.name like "%expresso%" // WHERE c.name LIKE "%expresso%"
sortBy (s.price.desc) // ORDER BY s.price DESC
drop (20) // OFFSET n
take (10) // LIMIT m
} yield (c.name, s.name) // SELECT c.name, s.name
Entorns de desenvolupament
[modifica]- La manera més vistosa és descarregar l'entorn web de desenvolupament Activator de lightbend.com (abans TypeSafe.com).[87]
- Per treballar a consola de comandes, cal descarregar el gestor de projectes SBT que per cada projecte descarregarà si no la té, la versió de Scala especificada pel fitxer de projecte (build.sbt). És convenient seguir l'estructura de directoris especificada per l'SBT.
- Una altra manera de treballar amb Scala és descarregant d'aquí[2] l'endollable per a Eclipse (o altres entorns de desenvolupament Java) que ja incorpora el compilador i les biblioteques de les quals, clicant, se'n pot examinar el codi font.
Scala a Eclipse
[modifica]A Eclipse[88] un cop incorporat l'endollable al menú Help->Install new software podrem crear un "Projecte Scala" que incorporarà automàticament les biblioteques de Scala al projecte si ho fem amb el menú de context de la barra lateral esquerre a l'opció "New -> Project -> Scala Wizards -> Scala Project" del menú de context de la barra lateral de projectes.
Scala segueix la mateixa estructura de mòduls (package) que el Java. Tanmateix podem posar diversos objects, classes i traits al mateix fitxer de codi font.
És habitual crear primer sobre el directori src, abreviació de source (codi font), un Package amb nom on crear els fonts, si volem facilitar l'estructuració modular o la inter-operabilitat amb paquets d'altres projectes.
Després podrem triar, al menú de context del package, els assistents (ang:wizards) de generació d'esquelets Scala Application, Scala Object, Scala Class o Scala Trait
Execució: per cada projecte cal crear una configuració d'engegada que podrem desar, especificant projecte, nom de l'objecte que conté el mètode main i possibles paràmetres de l'aplicació. Clicant el menú de context del projecte, opcions "RunAs -> Run configurations -> especificar la configuració o seleccionar-la -> botó Run"
Exemples
[modifica]Lector amb analitzador gramatical
[modifica]Basat en la classe JavaTokenParsers
[89] per aprofitar la decodificació de literals String.
La classe interna Parser
[90] adequa els operadors ((~>) seqüència, (|) alternativa, postfix (*) zero o més repeticions, postfix (+) una o més, postfix (?) zero o una) als combinadors de gramàtica regular específics de la classe, i també els que permeten l'extracció (^^) o bé l'especificació de resultat (^^^).
package test // fitxer: test/LaMevaApp.scala
/* Contingut del fitxer de dades amb el llenguatge de simulació de teclat que pretenem processar
Str "abcd"
Enter
Str "linia2"
Move Up
Move Dn
Move Left
Back
Move Right
Del
* /
object LaMevaApp extends App {
import scala.util.parsing.combinator.JavaTokenParsers
object MoveDirEnum extends Enumeration {
type TMoveDir = Value
val MoveDir_Up, MoveDir_Dn, MoveDir_Left, MoveDir_Right = Value
}
import MoveDirEnum._
// Simulació de pulsacions de teclat
// "sealed" permet comprovar exhaustivitat en l'encaix de patrons
abstract sealed class Cmd();
case class CmdStr(val str: String) extends Cmd() {
override def toString = "CmdStr " ++ str
}
case class CmdEnter() extends Cmd() {
override def toString = "CmdEnter"
}
case class CmdBack() extends Cmd() {
override def toString = "CmdBack"
}
case class CmdDel() extends Cmd() {
override def toString = "CmdDel"
}
case class CmdMove(val dir:TMoveDir) extends Cmd() {
override def toString = "CmdMove " ++ dir.toString
}
def escapçaDelimitadors(s: String): String = {
assume (s.length >= 2, "cas no previst"); // assumpció
s.slice(1, s.length -1) // substring del segon al penúltim
}
import scala.language.postfixOps
class MyParser extends JavaTokenParsers {
// produccions
def unOMesEspais = accept(' ')+
def zeroOMesEspais = accept(' ')*
def nl = zeroOMesEspais ~> accept('\n')
def strParam = stringLiteral ^^ { s => CmdStr(escapçaDelimitadors(s)) } // : Parser[Cmd]
def str = "Str" ~> unOMesEspais ~> (strParam | err("Str: manca param"))
def up = "Up" ^^^ MoveDir_Up // : Parser[TMoveDir]
def dn = "Dn" ^^^ MoveDir_Dn
def right = "Right" ^^^ MoveDir_Right
def left = "Left" ^^^ MoveDir_Left
def moveDir = (up | dn | left) ^^ { dir => CmdMove(dir) }
def move = "Move" ~> unOMesEspais ~> (moveDir | err("Move: manca param"))
def enter = "Enter" ^^^ CmdEnter()
def back = "Back" ^^^ CmdBack()
def del = "Del" ^^^ CmdDel()
def statement : Parser[Cmd] = str | move | enter | back | del
def statements : Parser[List[Cmd]] = rep1sep(statement, nl)
}
object MyParser {
import java.io.File
import scala.io.Source
def processaFitxer(parser:MyParser, nomFitxer: String): List[Cmd] = {
val file = new File(nomFitxer)
if (file.exists()) {
val contingut = Source.fromFile(file, "UTF-8").getLines().flatMap(_ ++: "\n").mkString
parser.parseAll(parser.statements, contingut) match {
case parser.Success(resultat,_) => resultat
case x => { println(x); Nil }
}
}
else { printf("el fitxer %s no existeix\n", nomFitxer); Nil
}
}
}
val parser = new MyParser()
args.toList match {
case nomFitxer :: Nil => // List(nomFitxer) també detecta llistes d'un sol element
{ val cmds = MyParser.processaFitxer(parser, nomFitxer)
cmds match {
case Nil => {println("no s'han trobat comandes"); sys.exit(1)}
case _ => { println("les comandes són: ")
cmds.foreach(cmd => println(cmd))
}
}
}
case _ => println("ús: nomprog nomFitxer")
}
}
# compilació a l'Eclipse: clicar al menú de context RunAs -> Run configurations
# -> doble-clic a Scala application -> New Configuration
# -> Ajustar el nom de l'objecte d'engegada (el que deriva de App o conté el mètode main)
# -> prémer botó Run
# compilació en consola de comandes
mkdir classes
scalac -d classes src/test/LaMevaApp.scala
# execució
scala -cp classes test.LaMevaApp
Cues amb prioritat sobre parells (prioritat, valor)
[modifica]Cal especificar explícitament l'ordenació sobre el primer del parell.
import scala.math.Numeric._
import scala.collection.mutable.{PriorityQueue => PQ}
object Main extends App {
val intOps = implicitly[ Integral[Int]]
val cuaDeParellsAmbPrioritatAlPrimerDelParell_DeMajorAMenor =
PQ((3,"b"),(1,"c"),(5,"a")) (intOps.on(_._1)) // Ordering explícit sobre el primer del parell
val cuaDeParellsAmbPrioritatAlPrimerDelParell_DeMenorAMajor =
PQ((3,"b"),(1,"c"),(5,"a")) (intOps.on({ case (k, v) => -k }))
for (pq <- List(cuaDeParellsAmbPrioritatAlPrimerDelParell_DeMajorAMenor,
cuaDeParellsAmbPrioritatAlPrimerDelParell_DeMenorAMajor)) {println("El cap és: " + pq.head)}
}
Referències
[modifica]- ↑ Scala - descàrregues(anglès)
- ↑ 2,0 2,1 Endollables Scala a diversos entorns de desenv. (anglès)
- ↑ 3,0 3,1 trait Application Arxivat 2014-09-25 a Wayback Machine. obsolet a 2.9, ang:deprecated, eliminat a 2.11
- ↑ trait App
- ↑ 5,0 5,1 Scala - Opcions d'execució(anglès)
- ↑ 6,0 6,1 scala-sbt.org(anglès)
- ↑ SBT - .sbt build definition(anglès)
- ↑ SBT - Esquema de directoris(anglès)
- ↑ Scala Language Specification(anglès)
- ↑ Tipus unificats(anglès)
- ↑ ScalaObject ha quedat obsolet (ang:deprecated) Arxivat 2014-10-05 a Wayback Machine.(anglès)
- ↑ 12,0 12,1 scala.Nothing Arxivat 2014-05-31 a Wayback Machine.(anglès)
- ↑ API - Symbol(anglès)
- ↑ 14,0 14,1 Equality of objects[Enllaç no actiu]
- ↑ scala.AnyRef(anglès)
- ↑ scala.Any(anglès)
- ↑ scala.collection.GenIterable(anglès)
- ↑ AnyRef.==(anglès)
- ↑ scala.math.Numeric(anglès)
- ↑ scala.math.Integral(anglès)
- ↑ scala.math.Fractional(anglès)
- ↑ Cota superior en els tipus(anglès)
- ↑ Cota inferior en els tipus(anglès)
- ↑ 24,0 24,1 Scala docum. - Variances(anglès)
- ↑ 25,0 25,1 25,2 Scala tour - Lower type bounds(anglès)
- ↑ Objectes extractors(anglès)
- ↑ Introduction to Monads in Scala(anglès)
- ↑ Dotty (versió futurista de Scala) vegeu "Trait parameters"(anglès)
- ↑ Explicitly Typed Self References(anglès)
- ↑ "Scala Component Abstractions" (anglès) Vegeu apartat 2.3 Selftype Annotations
- ↑ scala.Function1(anglès)
- ↑ scala.Function2(anglès)
- ↑ "Pattern Matching Anonymous Functions"(anglès)
- ↑ El tret PartialFunction(anglès)
- ↑ scala.util.control.Exception(anglès)
- ↑ Java try-with-resources(anglès)
- ↑ Loan pattern(anglès)
- ↑ java.io.Closeable(anglès)
- ↑ 39,0 39,1 Scala-ARM(anglès)
- ↑ Assercions Arxivat 2011-12-05 a Wayback Machine.(anglès)
- ↑ Adding ??? to Predef?(anglès)
- ↑ Is requires keyword is obsolete in Scala 2.8 (or removed)?
- ↑ Scala Tour - XML(anglès)
- ↑ A tour of Scala: Views
- ↑ Pimp my library(anglès)
- ↑ The "quick pimp" pattern Arxivat 2014-11-20 a Wayback Machine.(anglès)
- ↑ Mètodes amb aritat 0
- ↑ Views(anglès)
- ↑ TraversableViewLike, que conté el mètode force.(anglès)
- ↑ scala.collection.Iterator(anglès)
- ↑ scala.collection.immutable.Stream(anglès)
- ↑ La API de Scala (anglès)
- ↑ Manuals del llenguatge(anglès)
- ↑ Objecte Predef(anglès) tipus i funcions predefinits.
- ↑ Hayoo Arxivat 2010-03-22 a Wayback Machine.(anglès)
- ↑ Hoogle(anglès)
- ↑ Scalex - Hoogle-like documentation search engine for scala library(anglès)
- ↑ A new API search engine for Scala: scala-search.org[Enllaç no actiu](anglès)
- ↑ Scala Akka(anglès)
- ↑ ScalaSTM(anglès)
- ↑ Scala - Akka STM(anglès)
- ↑ Col·leccions, vista general(anglès)
- ↑ Scala parallel collections(anglès)
- ↑ El tret ParIterable(anglès)
- ↑ parallel.Splitter(anglès)
- ↑ Scala concurrent package(anglès)
- ↑ paquet scala.collection - apartat "Creating collections"(anglès)
- ↑ scala.collection.SeqLike(anglès)
- ↑ scala.collection.TraversableView(anglès)
- ↑ scala.collection.TraversableOnce Arxivat 2017-02-08 a Wayback Machine.(anglès)
- ↑ Scala.math.Numeric
- ↑ la interfície math.Ordering
- ↑ scala.collection.TraversableLike
- ↑ scala.collection.generic.CanBuildFrom(anglès)
- ↑ scala.collection.IterableLike
- ↑ scala.collection.mutable.Buffer(anglès)
- ↑ el tret Integral[T]
- ↑ scala.collection.Set Arxivat 2014-05-31 a Wayback Machine.(anglès)
- ↑ scala.collection.Map(anglès)
- ↑ API collection.immutable.StringOps Arxivat 2018-02-12 a Wayback Machine.(anglès)
- ↑ API collection.IndexedSeqLike Arxivat 2018-01-02 a Wayback Machine.(anglès)
- ↑ String interpolation(anglès)
- ↑ scala.util.matching.Regex(anglès)
- ↑ Scalaz(anglès)
- ↑ Learning Scalaz(anglès)
- ↑ Biblioteca slick de Typesafe(anglès)
- ↑ Activator de TypeSafe(anglès)
- ↑ Eclipse (Entorn de desenvolupament)(anglès)
- ↑ API de la classe JavaTokenParsers(anglès)
- ↑ API de la classe interna Parsers.Parser(anglès)
Enllaços externs
[modifica]- Lloc web d'Scala (anglès)