Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active April 2, 2023 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dacr/1b5cd607f483cc89dcc0cd1c58b36bbc to your computer and use it in GitHub Desktop.
Save dacr/1b5cd607f483cc89dcc0cd1c58b36bbc to your computer and use it in GitHub Desktop.
scala type class / published by https://github.com/dacr/code-examples-manager #66d83b63-e5bf-42c5-8727-8a6322217af8/4d68bef5f843a85716993bd8d0acb94a17a2afb9
// summary : scala type class
// keywords : scala, adt, language-feature, smart-constructor, @testable
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : 66d83b63-e5bf-42c5-8727-8a6322217af8
// created-on : 2021-04-05T17:50:27Z
// managed-by : https://github.com/dacr/code-examples-manager
// run-with : scala-cli $file
// ---------------------
//> using scala "3.1.2"
// ---------------------
// written after [John De Goes - 12 Steps To Better Scala (Part I)](https://youtu.be/71yhnTGw0hY)
// PREFER TYPE CLASSES OVER INTERFACES
// ----------------------------------------------------------------------
// SO DO NOT WRITE :
trait NumberLike[A] {self =>
def +(that:A):A
}
// *** BEFORE WITHOUT TYPE CLASS ***
case class RationalOO(n: BigInt, d:BigInt) extends NumberLike[RationalOO] {
def +(that: RationalOO):RationalOO = RationalOO(n*that.d+that.n*d,d*that.d)
}
def sumAllOO[A <: NumberLike[A]](l:List[A]):A = l.reduce(_ + _)
// so we'll have to support Byte, Short, Int, ...
// And comes the limit, we only support types which extends NumberLike !
// This is the limit of the object oriented approach
// =====================================================================================
// SO Instead :
abstract class Numeric[A] { // A type class with a single operation
def add(l: A, r:A):A
} // it will exists outside from Rational !
object Numeric {
def apply[A](implicit n: Numeric[A]):Numeric[A] = n
}
implicit class NumericSyntax[A](l: A) {
def +(r:A)(implicit n: Numeric[A]) = n.add(l,r)
} // the syntax class
// *** AFTER THANKS TO TYPE CLASS ***
case class Rational(n: BigInt, d:BigInt) // SO HERE NO INHERITANCE TO GIVE THE + CAPABIlITIES
// The implementation of the type class
implicit val RationalNumeric: Numeric[Rational] =
(l: Rational, r: Rational) => Rational(l.n * r.d + r.n * l.d, l.d * r.d)
// Thanks to the implicit the new capability is available to the Rational type
println(Rational(1,2)+Rational(2,3))
// So we can define a fully generic sumall for all type which "support" Numeric type class !
def sumAll[A: Numeric](l: List[A]): A = l.reduce(_ + _)
println(sumAll(List(Rational(1,2), Rational(2,3), Rational(1,7))))
// so for third party data types it becomes straightforward
// => just have to define new implicit val such as :
implicit val BigIntNumeric: Numeric[BigInt] = (l: BigInt, r: BigInt) => ???
// the interface is removed from the object
// so we got a type classes which allow to see similarities between totally different data types
// so we can add plus capability even to third parties types !
println("IT WILL BE QUITE EASIER TO DO IN SCALA 3 ! Not so easy in scala 2.x")
println("TYPE CLASSES ALLOW TO ABSTRACT ANY DATA TYPES EVEN THOSE COMING FROM THIRD PARTIES !")
println("=> A QUITE MORE POWERFUL SOLUTION THAN INTERFACES & OO STYLE !!")
// IT BRINGS EVEN MORE THINGS : AUTOMATIC DERIVATION
implicit val IntNumeric: Numeric[Int] = (l: Int, r: Int) => l + r
implicit def Tuple2Numeric[A: Numeric, B:Numeric]: Numeric[(A, B)] =
(l: (A, B), r: (A, B)) => (l(0) + r(0), l(1) + r(1)) // This is the derivation !
println(sumAll((1,1)::(3,4)::(6,9)::Nil))
println(sumAll((1,(1,3))::(3,(11,4))::(6,(13,9))::Nil)) // almost magic !
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment