Sunday 3 June 2012

Scala: Type Classes

Type classes were originally developed in Haskell as a disciplined alternative to ad-hoc polymorphism. Type classes have been shown to provide a type-safe solution to important challenges in software engineering and programming languages such as, for example, retroactive extension of programs. They are also recognized as a good mechanism for concept-based generic programming and, more recently, have evolved into a mechanism for type-level computation. Rephrasing in simple words: if U got conception of Context Bound in Scala U almost get type classes implementation in Scala.

Introduction

Let's move from the problem to solution and refactoring in meantime, to use Scala sugar. Imagine we have libraries class MyClass and don't have an access to the source code:

    class MyClass(val a: Int, val b: String, val c: Double)

To represent it as a String we can use a function:

    def toDisplay[T](d: T)(echo: Echo[T]): String = echo.echos(d)

and according to OO lets introduce class that can present a MyClass via String:

    trait Echo[A] {
        def echos(a: A): String
    }

and concreate implementation for MyClass:

    object myClassEcho extends Echo[MyClass] {
        def echos(a: MyClass): String = "[ a = %d, b = %s, c = %f ]" format (a.a, a.b, a.c)
    }

and usage:

toDisplay(mClass)(myClassEcho)

Implicit

Scala, the echo constraint can be implicitly passed by adding an implicit qualifier before the argument:

    def toDisplay[T](d: T)(implicit echo: Echo[T]): String = echo.echos(d)

Likewise potential candidate models can be considered by the compiler by being qualified with an implicit keyword:

    implicit object myClassEcho extends Echo[MyClass] ...

having fun from usage:

    toDisplay(mClass)

Context Bound

Last one variant for today - is using context bound sugar. To bring diversity let's declare Echo implementation via implicit val instead object. Additionally implicit declaration is called: the way to add a type to the class (Echo).

    implicit val myClassEcho = new Echo[MyClass]  ...

U can use both variants, this part isn't changed. toDisplay method we are going present via context bound:

    def toDisplay[T: Echo](d: T): String = implicitly[Echo[T]].echos(d)

[T: Echo] - means that type T should have associated parameterized type. And implicitly is a part of predef: def implicitly[T](implicit e: T) = e

Outcomes

- MyClass isn't aware about Echo, at least source isn't touched
- Need a second presentation for Echo - it's easy: put a new implicit val into the scope that will override previous:

    def toDispayXml(d: MyClass): String = {
        implicit val myClassEchoXml = new Echo[MyClass] {
            def echos(a: MyClass): String = "<mc a = \"%d\" b = \"%s\" c = \"%f\" />" format(a.a, a.b, a.c)
        }
        toDisplay(d)
    }

    toDispayXml(mClass)

Issues

Imagine polymorphism way, instead of echos method should be echo.

    trait Echo {
        def echo: String
    }

    class AEcho extends Echo {
        def echo() = "A"
    }

    class BEcho extends Echo {
        def echo() = "B"
    }

    List[Echo](new AEcho, new BEcho).foreach(x => println(x.echo))

Polymorphism works fine! But it's almost impossible to bring type class for each, because of Scala's typing limitations. 

No comments:

Post a Comment