M.C. Oscar Vargas Torres
@oscarvarto
Scala Developer and
(dys)functional programmer
add the skills the trainee will obtain
specify the agenda
Objects in Scala are used for singletons and utility methods.
object MySingleton {
var n: Int = 3
def utilityMethod1(): String = ???
}
Other languages have static methods or fields instead.
Scala supports OOP concepts, like classes:
// Definition
class Person(val firstName: String,
val lastName: String,
var age: Int)
// Usage
val p = new Person("Clark", "Kent", 30)
Check Classes tour.
An object of the same name than a class in the same source file
class Account
// Companion object
object Account {
}
Case classes example
case class Person(firstName: String,
lastName: String,
age: Int)
val
.apply
method automatically provided (no need to use new
)unapply
method automatically provided (pattern matching)toString
, equals
, hashCode
, and copy
unless explicitly provided.Programmers and Editors
sealed trait ProgrammingLanguage
case object CSharp extends ProgrammingLanguage
case object JavaScript extends ProgrammingLanguage
case object Python extends ProgrammingLanguage
case object Scala extends ProgrammingLanguage
sealed trait Editor
case object IntelliJ extends Editor
case object Emacs extends Editor
case object VSCode extends Editor
case object VisualStudio extends Editor
case class Programmer(name: String, progLang: ProgrammingLanguage)
One possibility
def chooseEditor(p: Programmer): Editor =
p.progLang match {
case CSharp => VisualStudio
case JavaScript => VSCode
case Python => Emacs
case Scala => IntelliJ
}
trait Shape {
def draw(): Unit
}
abstract class Quadrangle extends Shape { }
Traits can encode "interfaces"
class Person(val firstName: String,
val lastName: String,
var age: Int)
age
field.firstName
and lastName
have "getters" automatically.Called dynamic polymorphism also.
abstract class Person {
def name: String
def age: Int
}
case class Employer(val name: String,
val age: Int,
val taxno: Int)
extends Person
case class Employee(val name: String,
val age: Int,
val salary: Int)
extends Person
Creational | Structural | Behavioral | Behavioral (cont) |
---|---|---|---|
Factory | Adapter | Interpreter | Memento |
Abstract Factory | Bridge | Generics/Template | Flyweight |
Builder | Composite | Chain of Responsibility | Observer |
Prototype | Decorator | Command | State |
Singleton | Facade | Iterator | Strategy |
Proxy | Mediator | Visitor |
Java example:
import java.util.OptionalInt;
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final OptionalInt calories; // (per serving)
private final OptionalInt fat; // (g/serving)
private final OptionalInt sodium; // (mg/serving)
private final OptionalInt carbohydrate; // (g/serving)
// Code Elided
}
Private constructor:
private NutritionFacts(int servingSize,
int servings,
OptionalInt calories,
OptionalInt fat,
OptionalInt sodium,
OptionalInt carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
Builder pattern for NutritionFacts
:
static public class Builder {
private int servingSize = 0;
private int servings = 0;
private OptionalInt calories = OptionalInt.empty();
private OptionalInt fat = OptionalInt.empty();
private OptionalInt sodium = OptionalInt.empty();
private OptionalInt carbohydrate = OptionalInt.empty();
// Code elided
A method per field:
public Builder servingSize(int servingSize) {
this.servingSize = servingSize;
return this;
}
public Builder servings(int servings) {
this.servings = servings;
return this;
}
public Builder calories(int calories) {
this.calories = OptionalInt.of(calories);
return this;
}
public Builder fat(int fat) {
this.fat = OptionalInt.of(fat);
return this;
}
public Builder sodium(int sodium) {
this.sodium = OptionalInt.of(sodium);
return this;
}
public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = OptionalInt.of(carbohydrate);
return this;
}
build()
method calling NutritionFacts
constructor.
public NutritionFacts build() {
return new NutritionFacts(servingSize,
servings,
calories,
fat,
sodium,
carbohydrate);
}
Not necessary with Scala's case classes and default parameter values.
case class SNutritionFacts(servingSize: Int,
servings: Int,
calories: Option[Int] = None,
fat: Option[Int] = None,
sodium: Option[Int] = None,
carbohydrate: Option[Int] = None)
C# equivalent:
using LanguageExt;
using static LanguageExt.Prelude;
namespace DesignPatterns
{
public class NutritionFacts
{
public int ServingSize { get; } = 0;
public int Servings { get; } = 0;
public Option<int> Calories { get; } = None;
public Option<int> Fat { get; } = None;
public Option<int> Sodium { get; } = None;
public Option<int> Carbohydrate { get; } = None;
public NutritionFacts(int servingSize,
int servings,
int? calories = null,
int? fat = null,
int? sodium = null,
int? carbohydrate = null) {
ServingSize = servingSize;
Servings = servings;
Calories = Optional(calories);
Fat = Optional(fat);
Sodium = Optional(sodium);
Carbohydrate = Optional(carbohydrate);
}
}
}
FSharp implementation (different solutions may be possible):
type FNutritionFactsRecord =
{ ServingSize: int
Servings: int
Calories: int Option
Fat: int Option
Sodium: int Option
Carbohydrate: int Option
}
let defaultNutritionFacts =
{ ServingSize = 0
Servings = 0
Calories = None
Fat = None
Sodium = None
Carbohydrate = None
}
let cocaCola =
{ defaultNutritionFacts with
ServingSize = 1
Servings = 2
}
new
is not appropriate.clone
methodWe are going to present a simple problem and its solution using Scala case classes, then compare with other languages implementations you will finish.
Assume the following Point
and Triangle
definitions:
case class Point(x: Double, y: Double)
trait Shape {
def draw(): Unit
}
case class Triangle(p1: Point, p2: Point, p3: Point)
extends Shape {
override def draw(): Unit = ???
}
case class Square(p1: Point, p2: Point, p3: Point, p4: Point)
extends Shape {
override def draw(): Unit = ???
}
(A different encoding with typeclasses is possible)
Because both Point
and Triangle
are immutable case classes, you can simply share the instance references, and no clone would be necessary.
val t1: Triangle = // assume definition here
val t2: Triangle = t1 // sharing instance reference here
// The rest of the vertices remain unmodified.
val t3: Triangle = t1.copy(p1 = Point(0.0, 0.0))
public interface Shape {
void draw();
}
// A point
import lombok.Value;
import lombok.experimental.Wither;
@Value
public class Point {
@Wither float X;
@Wither float Y;
}
Exercise: try implementing this with vanilla Java :)
import lombok.Value;
import lombok.experimental.Wither;
@Value
public class Triangle implements Shape {
@Wither Point p1;
@Wither Point p2;
@Wither Point p3;
@Override
public void draw() {
// Draw this triangle
}
}
Exercise: try implementing this with vanilla Java :)
import lombok.Value;
import lombok.experimental.Wither;
@Value public class Square implements Shape {
@Wither float p1;
@Wither float p2;
@Wither float p3;
@Wither float p4;
@Override public void draw() {
// draw this square
}
}
Complete the exercise with the following starting point.
open System;
module Shape =
type IShape =
abstract member Draw: unit
type Point =
{ x: float; y: float }
type Triangle =
{ p1: Point; p2: Point; p3: Point
} interface IShape with
member x.Draw: unit = raise (new NotImplementedException "Triangle.draw")
Complete the exercise with the following starting point.
open System
open System.Drawing
type Triangle =
{ p1: Point; p2: Point; p3: Point }
type Square =
{ p1: Point; p2: Point; p3: Point; p4: Point }
type Shape =
| STriangle of Triangle
| SSquare of Square
let draw (s: Shape): unit =
match s with
| STriangle t -> raise (new NotImplementedException("draw a Triangle"))
| SSquare s -> raise (new NotImplementedException("draw a Square"))
{-# LANGUAGE DuplicateRecordFields #-}
data Point = Point { x :: Float, y :: Float }
data Triangle = Triangle
{ p1 :: Point, p2 :: Point, p3 :: Point }
data Square = Square
{ p1 :: Point, p2 :: Point
, p3 :: Point, p4 :: Point }
class Shape a where
draw :: a -> ()
instance Shape Triangle where
draw t = () -- Not implemented
Typeclasses will be reviewed in Module 8.