View Bound
To affect this, A must an implicit conversion to T so calling method on A calls implicit conversion A to T and then call real methodFromT.
class Echo[A](value: A) {
def echo() = println(value.toString)
}
2. Declaring implicit conversion from Into to Echo
implicit def int2echo (i:Int) : Echo[Int] = new Echo(i)
3. Declaring side effect method that can print anything that has an implicit conversion to Echo
def f[A <% Echo[A]](a: A) = a.echo()
4. Using this method:
f(3) // prints 3 to Console
As U can find we just extended functionality of class Int to class Echo. Source code of Int isn't touched.
A view bound is Scala's mechanism to enable the use of some type A
as if it were some type T (Real type). The typical syntax is this:
def func[A <% T](a: A)
= a.methodFromT
To affect this, A must an implicit conversion to T so calling method on A calls implicit conversion A to T and then call real methodFromT.
Imagine that for some enteties in the system we want to add
side effect: be able to print their state to Console, here is simple example implementation via VIew Bound construction:
1. Implementing class Echo that is going to do
our business - present a value: A as a String:
class Echo[A](value: A) {
def echo() = println(value.toString)
}
2. Declaring implicit conversion from Into to Echo
implicit def int2echo (i:Int) : Echo[Int] = new Echo(i)
3. Declaring side effect method that can print anything that has an implicit conversion to Echo
def f[A <% Echo[A]](a: A) = a.echo()
4. Using this method:
f(3) // prints 3 to Console
As U can find we just extended functionality of class Int to class Echo. Source code of Int isn't touched.
Variants:
As you will find View Boud is just a syntax sugar for currying with
implicit parameter. Despite View Bound is quite old one solution a lot of
libraries aren't using it but currying with implicit conversion.
def f1[A <% Echo[A]](a: A) = println(a.echo())
def f2[A](a: A)(implicit convert: A => Echo[A]) =
println(convert(a).echo())
When:
View bound is used mostly to
implement the Pimp My Library pattern. In
the situations when U want to add methods (behavior) to an existing class
(might U don't have access to source code), in situations where you want to
return the original type somehow. If you do not need to return that type in any
way, then you do not need a view bound.
Context Bounds
Context Bounds actually generalize View Bounds. It's covers almost the
same problems and using the same solution: implicit conversion. It was
introduced in Scala 2.8.0 Version and is mostly is used to implement Haskell's type
classes, of course with Scala's specific way.
While a view bound can be used with any classes, even simple types
(for example, A <% String), a context bound requires a parameterized
type, such asEcho[A]above, but never String or Int. It is used to summon
some class that can work with our type A.
Here is example:
1. Class Echo has a major changes comparing to previous example.
It doesn't wraps a value A but declares a service method that accepts value of
type A.
class Echo[A]() {
def echo(a: A) = a.toString
}
2. We are declaring variable of type Echo[Int]
that can be injected as implicit parameter to method on demand.
implicit val intEcho = new Echo[Int]()
3. While a A requires availability of paramererized type Echo[A],
implicitly method summons (via implicit declaration) instance of Echo[A] and
then we can call it methods. Method g declares that type A has an has an
associated Echo
def g[A: Echo](a: A) =
println(implicitly[Echo[A]].echo(a))
4. Putting it together
g(3) // Prints 3 to Console
Variants:
Context Bound as a View Bound is a syntax sugar for currying with
implicit parameter. Here are desugar example:
def g1[A: Echo](a: A) = println(implicitly[Echo[A]].echo(a))
def g2[A](a: A)(implicit instance: Echo[A]) =
println(instance.echo(a))
When:
Main target is implementation of type classes. Idea of this
pattern is implementation of alternative to OO inheritance by making
functionality available through a sort of implicit adapter pattern.
One of the popular examples in the past is Ordering:
def func[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
and old one example with Numeric:
def func[A : Numeric](a:
A, b: A) = implicitly[Numeric[A]].plus(a, b)
Another example is linked
to Scala's magic changes with array creation of type T. An Array initialization
on a parameterized type requires a ClassManifest to be available, for arcane
reasons related to type erasure and the non-erasure nature of arrays.
Runtime information is
necessary to create the right representation of Array[T]. One needs to provide
this information by passing a ClassManifest[T] into the method as an implicit
parameter:
def tabulate[T](len: Int, f: Int => T)(implicit m:
ClassManifest[T]) = {
val xs = new
Array[T](len)
for (i <- 0 until len)
xs(i) = f(i)
xs
}
As a shorthand form, a
context bound can be used on the type parameter T instead, giving:
def tabulate[T:
ClassManifest](len: Int, f: Int => T) = {
val xs = new
Array[T](len)
for (i <- 0 until len)
xs(i) = f(i)
xs
}
Great post!
ReplyDeleteThnx!
Thanks for the post...A good one..
ReplyDelete