Showing posts with label F-bounded polymorphism. Show all posts
Showing posts with label F-bounded polymorphism. Show all posts

Wednesday, March 4, 2015

F-bounded Type Polymorphism

Target audience: Intermediate
Estimated reading time: 4'


This post introduces and describes the concept of bounded polymorphism using self-referential type constraint.


Introduction

F-Bounded Type polymorphism or Bounded Quantification Polymorphism is parametric type polymorphism that constrains the subtypes to themselves using bounds.
Let's consider the following "classification" problem:


How can we guarantee that the SharpPencils bucket contains only sharp pencils, not small pencils or erasers?

Type polymorphism

The first attempt to solve the problem is to rely on parametric type polymorphism. To this purpose, we create a Pencils trait sub-classed by as a bucket for sharp pencils, SharpPencils and a bucket for small pencils, SmallPencils.
For the sake of simplification, we assume that Pencils defines only 2 methods: add (line 4) and pop (line 5) pencils.

1
2
3
4
5
6
7
8
9
trait Pencils[T] {
  private lazy val content = new mutable.ListBuffer[T]
 
  def add(t: T): Unit = content.append(t)
  def pop: List[T] = (content -= data.head).toList
}
 
class SharpPencils extends Pencils[SharpPencils]
class SmallPencils extends Pencils[SmallPencils]


This implementation does not guarantee that SharpPencils is the only bucket that contains the sharp pencils (line 8). Another bucket OtherPencils can be created with sharp pencils, too (line 9).

1
class OtherPencils extends Pencils[SharpPencils]


The solution is to specify constraints (or bounds) on the type of elements contained in each bucket.

Bounded polymorphism

The goal is to make sure that the bucket of specific type (or containing a specific type of pencils). The first step is to make sure that a bucket does not contain other items than a Pencils. This is accomplished by using the recursive (or F-Bound) type polymorphism
   trait A[T]<: ...="" a="" pre="">
The class Pencils is parameterized with one of its sub-type (line 1). None of the existing methods need to change.

1
2
3
4
5
6
trait Pencils[T <: Pencils[T]] {
  private lazy val content = new mutable.ListBuffer[T]
 
  def add(t: T): Unit = content.append(t)
  def pop: List[T] = (content -= data.head).toList
}

This implementation resolve the limitation raised in the previous paragraph. However there is nothing that prevent SharpPencils to contain an eraser or small pencils and SmallPencils to contain sharp pencils, as illustrated in the next code snippet.

// Won't compile!
class SharpPencils extends Pencils[Eraser]
 
 // The following code compiles!
class SharpPencils extends Pencils[SmallPencils]
class SmallPencils extends Pencils[SharpPencils]


Self-referential polymorphism

As a matter of fact, the most effective constraint on a inherited type is the self reference (line 2) that list the type allows for the method to execute.

1
2
3
4
5
6
7
8
9
trait Pencils[T <: Pencils[T]] {
  self: =>
    private lazy val content = new ListBuffer[T]
    def add(t: T): Unit = content.append(t)
    def pop: List[T] = (content -= data.head).toList
}
 
   // The following code fails to compile!
class SharpPencils extends Pencils[SmallPencils]


The declaration of SharpPencils as containing small pencils (line 9) fails to compile because it violates the self-referential type restriction.

References

Getting F-Bounded Polymorphism into Shape B. Greenman, F. Muehlboeck, R. Tate - Cornell University

Friday, October 5, 2012

Type Class Polymorphism and Monads

Target audience: Advanced

Overview

Most of software developers associate the concept of polymorphism to dynamic binding & class inheritance available in the most common object oriented programming languages such as C++ and Java. As part of its functional programming "DNA", Scala provides developers with  alternative polymorphism approaches (beside the ubiquitous method override).

Note: For the sake of readability of the implementation of algorithms, all non-essential code such as error checking, comments, exception, validation of class and method arguments, scoping qualifiers or import is omitted.


Object-Oriented Polymorphism

Java or C++ programmers are familiar with this implementation of polymorphism as described in the example below. A trait the key behavior of a collection by declaring two method += to add a new item into the collection and -= to retrieve the first item from the collection.

trait Collection[T <: Double] {                           
   def += (t:T) : Boolean
   def -= : T 
}

The class, OrderedCollection implements ordered collection of type inherited from Double by defining the two abstract methods += & -=. This class specializes the behavior of the trait by ordering the elements in the collection.

class OrderedCollection[T <: Double] extends Collection[T] {
   import scala.collection.mutable.TreeSet
 
   var c = TreeSet.empty(Ordering.fromLessThan[T]((x:Double, y:Double) => (x < y)))
   override def += (t:T) : Boolean = c.add(t)
   override def -= : T = { 
     val top = c.head
     c.remove(top)
     top 
   }
}

The following code snippet illustrate a simple use case for the ordered collection. Note the declaration of the type of the collection instance, thisCollection is not required and added for the sake of clarity.

val thisCollection: Collection[Double] = new OrderedCollection[Double]
thisCollection += 9.556
thisCollection += 45.7
thisCollection += 4.9

println(thisCollection)


Type Class Polymorphism 

Scala supports Type class polymorphism, also referred as Ah-hoc polymorphism in technical articles. Scala also supports Ah-Doc polymorphism which the ability to create and apply generic polymorphic operators or functions to arguments of different types. Ad hoc polymorphism lets you add features to a type on demand.
Let's define a Wrapper type (trait) which basic purpose is to wrap an element into a collection M[_] of this element. The Wrapper type class declares the operators apply and get that is to be implemented by wrapper associated to a predetermined collection.
- apply creates a collection M[T] of element of type T.
- get retrieves the first and only element of the collection.

Note: The type of the element in the collection is not a parameter type for the Wrapper: T is the type parameter for the collection and type M[_] is the parameter type for the wrapper. The Wrapper trait is defined as:

trait Container[M[_]] {
  def apply[T](t: T): M[T]
  def get[T](c: M[T]): T
}

We can create a type class ArrayBuffer to "wrap" any object by overriding the apply and get operators. The implicit conversion from ArrayBuffer to Wrapper is obviously independent from the type T of the contained element (validation of method argument is omitted).

implicit val arrayBuffer = new Container[ArrayBuffer] {
  override def apply[T](t: T) : ArrayBuffer[T] =  ArrayBuffer[T](t)
  override def get[T](ab: ArrayBuffer[T]) : T = ab.head 
}

def compose[M[_]: Container, X, Y](c1: M[X], c2: M[Y]) = {
   val c = implicitly[Container[M]]
   c(ab.get(c1), ab.get(c2))
}


Monads

One of the most known application of type class polymorphism is the monadic representation of collection. The concept and theoretical foundation of monads is discussed in the previous post. Monads define the minimum set of operations for a functional category of objects. Scala standard library includes quite a few of categories and their associated monadic representation: List[T], Option[T], ..
Let's extend our simple Wrapper to define a Monad for collection containing a single element. The monad must define a constructor, apply, a functor, map that converts a single element collection into a another single element collection by transforming its only element and a flatMap that generate a new single element collection from the element of an existing collection.

trait _Monad[M[_]] {
  def apply[T](t: T): M[T]
  def map[T, U](m: M[_])(f: T => U): M[U]
  def flatMap[T, U](m: M[_])(f: T => M[U]): M[U]
}

As with the trait wrapper we can create a implicit conversion from any single element ArrayBuffer into its associated monad to monadic operators can be used transparently by the client code.

implicit val mArrayBuffer = new _Monad[ArrayBuffer] {
  override def apply[T](t:T): ArrayBuffer[T] =  ArrayBuffer[T](t)
  override def map[T, U](m: ArrayBuffer[T])(f: T => U): ArrayBuffer[U] = ArrayBuffer[U](f(m.head))
  override def flatMap[T, U](m: ArrayBuffer[T])(f: T => ArrayBuffer[U]): ArrayBuffer[U] = f(m.head) 
}

Monads will be discussed in length in a future post.


References