case class Circle(radius: Float) def area(square: Square): Float = Math.pow(square.side, 2).toFloat def area(rectangle: Rectangle): Float = rectangle.a * rectangle.b def area(circle: Circle): Float = (Math.PI * Math.pow(circle.radius, 2)).toFloat def compareArea[ A if there's a function area(A):Float, B if there's a function area(B):Float, ](a: A, b: B): Float = area(a) - area(b) compareArea(Square(10), Circle(5)) 4
Square, Rectangle, Circle support area def area(square: Square): Float = ... def area(rectangle: Rectangle): Float = ... def area(circle: Circle): Float = ... defined ad-hoc (specifically for certain types, and independently from their definition) 6
} def compareArea[A: HasArea, B: HasArea](a: A, b: B): Float = area(a) - area(b) Type variables A and B can only be instantiated to a type whose members support the operations associated with typeclass HasArea.1 1 https://en.wikipedia.org/wiki/Type_class 7
(implementation) -- Haskell data Square = Square { side :: Float } deriving Show data Rectangle = Rectangle { a :: Float, b :: Float } deriving Show data Circle = Circle { radius :: Float } deriving Show class HasArea a where area :: a -> Float instance HasArea Square where -- instance of HasArea for Square area x = side x ^^ 2 instance HasArea Rectangle where -- instance of HasArea for Rectangle area x = a x * b x instance HasArea Circle where -- instance of HasArea for Circle area x = pi * radius x ^^ 2 10
A CERTAIN TYPECLASS -- Haskell class HasArea a where area :: a -> Float ... compareArea :: HasArea a => HasArea b => a -> b -> Float compareArea x y = area x - area y 11
Show data Rectangle = Rectangle { a :: Float, b :: Float } deriving Show data Circle = Circle { radius :: Float } deriving Show class HasArea a where area :: a -> Float instance HasArea Square where area x = side x ^^ 2 instance HasArea Rectangle where area x = a x * b x instance HasArea Circle where area x = pi * radius x ^^ 2 compareArea :: HasArea a => HasArea b => a -> b -> Float compareArea a b = area a - area b *Main> compareArea Circle { radius = 3 } Rectangle { a = 2, b = 3 } 22.274334 12
side :: Float } deriving Show instance HasArea Square where area x = side x ^^ 2 ‑ case class Square(side: Float) implicit val squareHasArea: HasArea[Square] = new HasArea[Square] { def area(x: Square): Float = Math.pow(x.side, 2).toFloat } 17
b => a -> b -> Float compareArea a b = area a - area b ‑ def compareArea[A, B](a: A, b: B) (implicit aHasArea: HasArea[A], bHasArea: HasArea[B]): Float = aHasArea.area(a) - bHasArea.area(b) // or, equivalently (desugars to equivalent form) def compareArea[A: HasArea, B: HasArea](a: A, b: B): Float = implicitly[HasArea[A]].area(a) - implicitly[HasArea[B]].area(b) 18
for class C and type T which is neither defined in the module where C is defined nor in the module where T is defined.2 2 https://wiki.haskell.org/Orphan_instance 23
inherited values/defs; if one is available in both, ambiguous implicit ▸ among imported or inherited values/defs, the one defined in the most specific type 2. companion object of the datatype or typeclass 28
▸ (discoverability) where do I find available instances for a certain typeclass? ▸ (coherence, maintainability) what instance am I using when multiple are available? 29
(whenever possible) ▸ define blanket instances in companion objects of target typeclass ▸ be conservative and follow a convention for orphan and blanket instances (more on this later) 33
you control ▸ group all orphan instances in a package, allow selective import ▸ avoid overlapping instances, unless there's a specific usability need 47