/* A class is an abstraction over objects */
/* constructor args declared using val to expose publicly and can declare default values */
class Foo(val arg1: Long, val arg1: Double, val arg3: Int = 0) {
val someValue: Int = ??? /* public, immutable */
var someVariable: Long = ??? /* public, mutable */
def apply(arg1: Int) = ??? /* applies shorthand e.g instanceOfFoo(46) */
def Method1: Int = ???
def Method2: String = ???
}
/* A class is instantiated using the 'new' keyword */
val instanceOfFoo = new Foo(123L, 456.67)
object MySingleton {
... /* contains same body as what can be declared in a `class` */
}
/* A companion-object are declared in _same_ file as companion-class, and share the same name */
class Person(val name: String, val age: Int)
/**
* The companion-object methods are like Java static methods, but not exposed in class objects.
* Use companion-objects to implement multiple constructor using `apply` method
*/
object Person {
def apply(val name: String, val dob: java.util.Date): Person = {
new Person(name, Person.getAge(dob))
}
/* returns an age */
def getAge(val date: Date): Int = ???
}
/**
* case-classes are super classes:
* - a field for each constructor value
* - default toString() method
* - sensible equals() and hashCode() methods
* - a copy() method. Leverages constructor keywords concept
* - default apply() method. Same signature as class constructor
* - used for pattern-matching
*/
case class Male(name: Sting, age: Int)
case class Female(name: String, age: Int)
val john = Male("John Doe", 21)
val jane = john.copy(name="Jane Doe")
/* Traits are abstractions over classes. Mixture of Java Interface and Abstract-class*/
seal trait Visitor {
def id: Long
def createdAt: java.util.Date
def age: Long = ??? /* now - createdAt */
}
final case class Anonymous(id: Long, createdAt: Date = new Date()) extends Visitor
final case class User(id: Long, email: String, createdAt: Date = new Date()) extends Visitor
/**
* Traits: Best practice
* - Use `def` over `val` to define values
* - Seal your traits. Enforces that subtypes be declared in same file
* - Final your case classes
*/
This pattern can be described as:
A has a B and a C
e.g. Visitor has an ID and an Email
and can be modelled as
case class A(b: B, c: C)
or
trait A {
def b: B
def c: C
}
This pattern can be described as
A is a B or C
for example Feline is a Lion or Tiger
and can be modelled as
sealed trait A
final case class B() extends A
final case class C() extends A
Data that can be expressed by either of the two patterns above are regarded as Algebraic Data Types.
Structural recursion is the decomposition of algebraic data types. Structural recursion can be applied using polymorphasim or pattern matching.
This pattern is commonly found in OO
The structural resursive pattern for product-type data structures can be applied as follows
/**
* Suppose you have a class that you want deconstruct into it's parts,
* simply define an abstract method in the base trait and implement it
* in a concrete implementation
**/
sealed trait A {
def foo: String = "I'm an A"
}
final case class B extends A {
override def foo: String = "I'm a B"
}
The structural resursive pattern for sum-type data structures can be expressed as
/**
* Suppose you have a class that satisfies the sum-type (logical AND) algebraic data type
* pattern, and you want to deconstruct it into it's parts, you can simply refer to it's
* part e.g you can define a method (f) and refer to values `b` and `c` in `f`
**/
case class A(b: B, c: C) {
def f: F = ??? //you can access b and c
}
/**
* Suppose you have a class that satisfies the product-type (logical-OR) pattern.
* Using pattern-matching to deconstruct the class, match on the constructors
*/
case class A(b: B, c: C)
object someClassADeconstructor {
def f(a: A): F = a match {
case A(b, c) => ??? //you can access b and c
}
}
/**
* Suppose you have a class that satisfies the sum-type (logical-AND) pattern.
* Using pattern-matching to deconstruct the class, match on the type
*/
sealed trait A
final case class B(arg1: Int) extends A
final case class C(arg2: String) extends A
object someClassDeconstructor {
def f(a: A): F = a match {
case B(arg1) => ??? // you can access arg1
case C(arg2) => ??? // you can access arg2
}
}
/**
* Recursive ADTs consist of atleast two cases: a base case and a recursive case
*/
sealed trait BaseTrait
final case class BaseCase extends BaseTrait
final case class RecursiveCase(recursive: BaseTrait) extends BaseTrait
// example of
def f( r: BaseTrait ): Int = r match {
case BaseCase => 0 //some terminating value aka _identifier_
case RecursiveCase( base ) => f(base)
}
/**
* Generics can be applied to classes, traits and methods.
* The syntax [A, B , C] is called a type parameters.
**/
trait Name[A](...) {...}
case class Name[A](...) {...}
def generic[A](arg: A): A
If A
of type T
is a B
and C
sealed trait A[T]
final case class B[T] extends A[T]
final case class C[T] extends A[T]
Function types are written like (A, B) => C
where A
and B
are parameter types, while C
is the result type.