Functionnal Programming Notes (Scala)
Strucutres algébriques
composition interne *] DeGr[Demi-groupe: Magma dont * est associative] Mo[Monoide: Demi-groupe doté d'un
élément neutre pour *] MoCo[Monoide Commutatif: Monoide
dont * est commutative] Gr[Groupe: Monoide admettant un
élément symétrique pour *
pour chacun de ses éléments]
\(G=Monoide(*,e)\) est un groupe \(\Leftrightarrow \forall x\in G,\exists x^{-1}\in G,x*x^{-1}=e\)
Cats
Semigroup
Cats’ semigroup implem (import cats.Semigroup
):
trait Semigroup[A] {
def combine(x: A, y: A): A
}
Monoid
Cats’ semigroup implem (import cats.Monoid
):
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
Functor
Abstraction over a type constructor F[_]
(can be List[Int]
, Map[Int, String]
, Double
) providing ability to map
over it.
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
Note: The _
used is just a convention: Functor[F[_]]
is the same as Functor[F[T]]
but as you do not use T
in the Functor, it is clearer to name it in this anonymous fashion.
But, this will compile thanks to _
:
class C[F[_]](f: F[_])
but this won’t (not found: type T):
class C[F[T]](f: F[T])
To name you’re force to add it as type parameter too. This compiles:
class C[T, F[T]](f: F[T])
Monads Examples
Option[A]
Alternative to OOP’s null
.
Two subtypes:
val o = Some(v)
:final case class Some[+A](val x : A) extends scala.Option[A]
val o = None
:case object None extends scala.Option[scala.Nothing]
For any
o: Option[T]
, the first common supertype too
andNone
isT
.
Implicits
Implicit conversions
This
((i: Int) => i)("1")
gives
type mismatch;
found : String("1")
required: Int
but this compiles just fine:
implicit def StringToInt(s: String) = Integer.parseInt(s)
((i: Int) => i)("1")
Use Case: Decorator Pattern with implicit class
Suppose we want to be able to call .show()
on a DeltaTable
(optionally refer to see delta.io) that has not such a method.
Two approaches in OOP:
- Try to achieve this with a classic Decorator pattern:
class ShowableDeltaTable(deltaTable: DeltaTable) extends DeltaTable{ def show() = { deltaTable.toDF.show() } // And many method delegating to `this.deltaTable` the calls to DeltaTable's behaviors. override def [...] = this.deltaTable.[...] override def [...] = this.deltaTable.[...] override def [...] = this.deltaTable.[...] }
- This would be a mess.
- Simple inheritance:
class ShowableDeltaTable(deltaTable: DeltaTable) extends DeltaTable{ def show() = { this.toDF.show() } // And many method delegating to `this.deltaTable` the calls to DeltaTable's methods that return a DeltaTable. def as(alias: String): DeltaTable = new ShowableDeltaTable(df.as(alias), deltaLog) }
- With this solution the methods returning a
DeltaTable
likedef as(alias: String): DeltaTable
are problematic in that they make us lose ourShowableDeltaTable
type.
- With this solution the methods returning a
Actually both Decorator and inheritance does not compile since DeltaTable’s constructor is private.
So here come the saver that address all the previous issues in term of readability:
implicit class ShowableDeltaTable(deltaTable: DeltaTable){
def show() = deltaTable.toDF.show() // Delegation pattern
}
that make any instance of DeltaTable showable:
deltaTable.show()
deltaTable.as("alias").show()