Baixe o app para aproveitar ainda mais
Prévia do material em texto
Traits, Case classes & Objects This document provides the basics of case classes, objects and traits. In the examples proposed here, the classes, objects and traits should go into a scala file. Multiple classes and objects can exist inside a scala file. The tests in the examples on this document, are simple and can be tried in a scala worksheet. Traits Traits are similar to Java 8’s interfaces. Classes and objects can extend traits but traits cannot be instantiated. Traits are a fundamental for code reuse in Scala. They encapsulate method and field definitions, which can then be reused by mixing them into classes. A class must inherit from just one superclass, but a class can mix in any number of traits. A trait can be as simple as: trait Expr They could define an abstract values, which every concrete class must implement: trait Weight { def value: Int def unit: String } The value to implement can become a parameter or an internal value: final case class KiloGram(value: Int) extends Weight { val unit: String = “kg” } Or a little more complex: trait Similarity [A] { def isSimilar(e: A): Boolean def isNotSimilar(e: A): Boolean = !isSimilar(e) } There two methods isSimilar (abstract method) and isNotSimilar with a concrete implementation. Consequently, classes that integrate this trait only need a concrete implementation for isSimilar. Notice that this is a generic trait with type A defined as generic. Thus, the Dog class, if using the Similarity trait, needs to provide an implementation of the method isSimilar: final case class Dog(name: String, age: Int) extends Similarity[Dog] { override def isSimilar(d: Dog) = this.age == d.age } val d1 = Dog("Ace", 3) val d2 = Dog("Pebbles",5) val d3 = Dog("Otis", 3) val d4 = Dog("Ace", 3) assert(d1.isSimilar(d2)==false) assert(d1.isSimilar(d3)==true) Case Classes Scala class definition is much smaller than the definition in Java. Case classes should be final. Here an example of a person class: final case class Person(name: String, zip: Int) { def <(p: Person): Boolean = this.name < p.name } Case classes in Scala are parameterized with constructor arguments. The two constructor arguments, name and age, are both public and immutable. Member functions are public by default. To create a private member function, a private keyword must be added to its definition. The class defines a method “>”, (this is a possible method name in Scala) which compares the person’ names. Methods toString, equals, hashCode, and copy are automatically generated, but can be supplied. Case classes are often used with pattern matching mechanisms due to their equality structural comparisons. Observe the following: Person("Ana", 20) match { case Person(name, zip) => s"Name: $name ; Zip: $zip" } //> res0: String = Name: Ana ; Zip: 20 Person("Ana", 20) == Person("Jose", 30) //> res1: Boolean = false Person("Ana", 20)== Person("Ana", 20) //> res2: Boolean = true An example based on the “Programming in Scala” book will be presented. The domain of interest is arithmetic expressions consisting of variables, numbers, and unary and binary operations. sealed trait Expr final case class Var(name: String) extends Expr final case class Number(num: Double) extends Expr final case class UnOp(operator: String, arg: Expr) extends Expr final case class BinOp(operator: String, left: Expr, right: Expr) extends Expr The trait being sealed means that it can only be extended on this file. The final keyword has the same meaning as in Java. Using the above domain, a function to simplify expressions can be defined: def simplify(expr: Expr): Expr = expr match { case UnOp("-", UnOp("-", e)) => e case UnOp("-", Number(n)) if (n<0) => Number(-n) case BinOp("*", e, Number(1)) => e case BinOp("+", e, Number(0)) => e case _ => expr } The simplification process can be tested: simplify(UnOp("-", UnOp("-", Var("x")))) // Var(x) simplify(UnOp("-", Number(-1))) // Number(1) simplify(BinOp("+", Number(3.5), Number(0))) // Number(3.5) simplify(BinOp("*", Number(3.5), Number(1))) // Number(3.5) In the pattern matching simplify function, patterns are tried in the order in which they were written, just like the switch case statement in Java. In the second case there is a pattern guard, which filters only negative numbers. Pattern guards can associate a condition to the pattern matching and validate only if both the pattern and the condition hold. Once a case is valid, the result of the pattern matching is found. Exercises 1. Clone project lab04 and complete the ItemOps object, in the file Item.scala, so that it passes the tests. 2. Complete the TreeOps object, in the file Tree.scala, so that it passes the tests. Objects Singleton Objects Methods and values that are not associated with individual instances of a class can belong in singleton objects. This characteristic is achieved by using the keyword object instead of class. A singleton object definition is similar to a class definition. Here is an example of xml handling in scala. The function values is supposed to return a list with all the texts contained in elements with a certain label. // include xml in scala: // https://github.com/scala/scala-xml/wiki/Getting-started object XmlOps { def values(xml: scala.xml.Elem, label: String): Seq[String] = { for (e <- (xml \\ label)) yield e.text } } To call the values function, we define an xml object and provide it to the function. The definition of an xml object in scala can be done using the xml syntax directly : val xml = <div class="content"><p>Hello</p><p>world</p></div> XmlOps.values(xml,"p") // List(Hello, world) Companion Objects In Java a class with both instance methods and static methods are often needed. In Scala, you achieve this by having a class and a “companion” object. A singleton object with the same name as the class is called that class’s companion object. The class and its companion object have to be defined in the same source file. The class is called the companion class of the singleton object. A class and its companion object can access each others’ private members. Smart Constructors It is common to have companion objects be used as factories for the smart construction of valid domain objects. For instance, the following definition final case class DayOfWeek(d: Int) The d in DayOfWeek is supposed to be 1,2,..,7 but it is possible to build the 23 rd day of week using the case class. To limit the classe’s possible values, a smart constructor will be constructed. The class will also be modified: final case class DayOfWeek private (d: Int) object DayOfWeek{ private def unsafeDayOfWeek(d: Int): DayOfWeek = DayOfWeek(d) private val isValid: Int => Boolean = { i => i >= 1 && i <= 7 } def from(d: Int): Option[DayOfWeek] = if (isValid(d)) Some(unsafeDayOfWeek(d)) else None } With this definition, the constructor is private, do it is not callable unless from the companion object. The following call will not compile: DayOfWeek(1) Only the following call will compile, but not all will return a valid day of week. DayOfWeek.from(0) // None DayOfWeek.from(1) // Some(DayOfWeek(1)) DayOfWeek.from(7) // Some(DayOfWeek(7)) DayOfWeek.from(8) // None Opaque Type Aliases Opaque types aliases provide type abstraction without any overhead. Opaque types can be enhanced with through extension methods. Here, Inside object BankingMoney, Money is a BigDecimal. Outside, it is a new type with only the method to. Inside BankingBalance, Balance is a BigDecimal. This allows the representation of a Balance remaining a Balance even if adding or subtracting Money. Additionally extension methods can provide smart constructors. object BankingMoney: opaque type Money = BigDecimal object Money: def from(bi: BigDecimal): Money = bi extension (b: Money) def to: BigDecimal = b object BankingBalance: import BankingMoney.Money opaque type Balance= BigDecimal object Balance: def from(bi: BigDecimal): Balance = bi extension (b: Balance) def to: BigDecimal = b def + (m: Money): Balance = b + m.to def - (m: Money): Balance = b - m.to Exercises 1. Implement the Student case class, in project lab04, with attributes firstName and lastName, which consists only of letters, and age, which must be greater than zero. Create a smart constructor which does not allow values that do not belong to the domain. Provide an auxiliary form of construction that accepts a long name such as “Ana Júlia Silva” and an age. The implementation should pass the tests. 2. Implement opaque types StringWithOnlyLeters, StringWithLettersAndSpaces and PositiveInteger. These types should be defined in the SimpleTypes object. The implementation should pass the tests. 3. Implement the OtherStudent case class, similar to the Student class, but using the opaque types for StringWithOnlyLeters, StringWithLettersAndSpaces and PositiveInteger. The implementation should pass the tests. Traits Case Classes Exercises Objects Singleton Objects Companion Objects Smart Constructors Opaque Type Aliases Exercises
Compartilhar