How do I get an instance of the type class associated with a context bound?
Asked Answered
P

3

15

Note: I'm posing this question to answer it myself, but other answers are welcome.

Consider the following simple method:

def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y)

I can rewrite this using a context bound as follows

def add[T: Numeric](x: T, y: T) = ??.plus(x,y) 

but how do I get an instance of the Numeric[T] type so that I can invoke the plus method?

Pullman answered 7/12, 2010 at 3:15 Comment(0)
P
24

Using the implicitly method

The most common and general approach is to use the implicitly method, defined in Predef:

def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y)

Obviously, this is somewhat verbose and requires repeating the name of the type class.

Referencing the evidence parameter (don't!)

Another alternative is to use the name of the implicit evidence parameter automatically generated by the compiler:

def add[T: Numeric](x: T, y: T) = evidence$1.plus(x,y)

It's surprising that this technique is even legal, and it should not be relied upon in practice since the name of the evidence parameter could change.

Context of a Higher Kind (introducing the context method)

Instead, one can use a beefed-up version of the implicitly method. Note that the implicitly method is defined as

def implicitly[T](implicit e: T): T = e

This method simply relies on the compiler to insert an implicit object of the correct type from the surrounding scope into the method call, and then returns it. We can do a bit better:

def context[C[_], T](implicit e: C[T]) = e

This allows us to define our add method as

def add[T: Numeric](x: T, y: T) = context.plus(x,y)

The context method type parameters Numeric and T are inferred from the scope! Unfortunately, there are circumstances in which this context method will not work. When a type parameter has multiple context bounds or there are multiple parameters with different context bounds, for example. We can resolve the latter problem with a slightly more complex version:

class Context[T] { def apply[C[_]]()(implicit e: C[T]) = e }
def context[T] = new Context[T]

This version requires us to specify the type parameter every time, but handles multiple type parameters.

def add[T: Numeric](x: T, y: T) = context[T]().plus(x,y)
Pullman answered 7/12, 2010 at 3:33 Comment(4)
Clever hack with the context method!Curcio
Boy, if I thought anyone was relying on the name of the evidence parameter that way I'd change it once a week or so... also, the technique is not legal in my jurisdiction, but maybe the laws are different where you live.Silicone
@extempore It works in the 2.8.1 REPL, but I'm glad to hear it's not legal in trunk (assuming that's your jurisdiction). :-)Pullman
would be nice to be able to write def add[T: Numeric](x: T, y: T) = _.plus(x,y)Glad
D
7

At least since Scala 2.9 you can do the following:

import Numeric.Implicits._
def add[T: Numeric](x: T, y: T) = x + y

add(2.8, 0.1) // res1: Double = 2.9
add(1, 2) // res2: Int = 3
Daimon answered 21/5, 2011 at 15:34 Comment(0)
P
4

This answer describes another approach that results in more-readable, self-documenting client code.

Motivation

The context method that I described previously is a very general solution that works with any type class, without any additional effort. However, it may be undesirable for two reasons:

  • The context method cannot be used when the type parameter has multiple context bounds, since the compiler has no way to determine which context bound is intended.

  • The reference to the generic context method harms readability of the client code.

Type-class-specific methods

Using a method that is tied to the desired type class makes client code much more readable. This is the approach used in the standard library for the Manifest type class:

// definition in Predef
def manifest[T](implicit m: Manifest[T]) = m

// example usage
def getErasure[T: Manifest](x: T) = manifest[T].erasure

Generalizing this approach

The main drawback of using type-class-specific methods is that an additional method must be defined for every type class. We can ease this process with the following definitions:

class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e }
object Implicitly { def apply[TC[_]] = new Implicitly[TC] }

Then a new type-class-specific implicitly-style method can be defined, for any type class:

def numeric = Implicitly[Numeric]
// or
val numeric = Implicitly[Numeric]

Finally, client code can use the Implicitly as follows:

def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y)
Pullman answered 13/7, 2011 at 22:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.