Friday, June 19, 2015

Scala View Bound vs. Context Bound

Target audience: Intermediate
Estimated reading time: 15'


This post illustrates the conversion of class using view bound parameter types into class using context bound types.

Overview

There have been some discussion of deprecating view bound in Scala 2.12. Binding a parameterized type T to a predefined and known type using view (i.e. implicit function conversion) is quite convenient. I have used (or even abuse) this construct in writing machine leaning-based applications.
There are few options for replacing view bound class parameter types with any kind of ad-hoc polymophism techniques. I chose to convert my legacy code by binding the class parameter type to a context instead of a view.

Converting class parameter type binding

First, let's look at binding the parameterized type, T of a class Classifier to the primitive type Double (line 3). The binding is actually implemented by defining an implicit conversion function f: T => Double (line 6) as described in the following code snippet. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def sqr(x: Double): Double = x*x

class Classifier[T <: AnyVal](
   xt: Vector[Array[T]], 
   yt: Vector[Double])
  (implicit f: T => Double) {
 
 def loss(weights: Array[T]): Double = {
   xt.zip(yt).map{ case(x, y) => 
     y - x.zip(weights).map{ case(_x, w)
       => _x*w}.sum }.map(sqr(_)).sum
}

In this example, the observed features and the model parameters weights have the same type: array of element T (line 8). The input time series xt is a vector of observations and the labels (or expected outcome) yt is a vector of single variable observation of type Double (line 9). The implicit conversion apply to the features and weights. Scala provides developers with shorter alternative notation T <% Uas follows: 

class Classifier[T <% Double](
   xt: Vector[Array[T]], 
   yt: Vector[Double]) { }

Replacing the binding of the class parameters for many classes would be tedious and error prone. One solution is to create a template or pattern that can be applied to several section of the code base.
The process of binding the type T of a class parameter to another known parameter type consists of defining a value (or context) Feature for the type T (line 1). The context binding is an implicit value conversion T => Feature[T] (line 3). 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Feature[T]{ def apply(x: T): Array[Double] }
 
class Classifier[T : Feature](
   xt: Vector[T],
   yt: Vector[Double]) {
  
 def loss(weights: T): Double = {
   xt.zip(yt).map{case(x, y) => {
     val mapper: Feature[T] = implicitly[Feature[T]]
     y - mapper(x).zip(mapper(weights)).map{ case(x, w) =>x*w}.sum
    }}.map(sqr(_)).sum
  }
}

The Feature trait defines the context for the conversion which is actually implemented by the apply method (line 1). The view binding implicit function T => Double defined in the first implementation of the Classifier class is replaced by the mapping (or conversion) method: Feature.apply: T => Array[Double]
The mapping function mapper is implicitly declared using the implicitly.

The notation T: Feature specifies the implicit value, feature: It is equivalent to the more descriptive declaration of the Classifier class: 

1
2
3
class Classifier[T](
   xt: Vector[T],
   yt: Vector[Double])(implicit feature: Feature[T])

Important note
A class parameterized type T cannot be bound to a primitive type P using a context: the developer has to define either an implicit view (T => P) or a context (or wrapper) W for the type T => W[P].

References