Simão Martins
Simão Martins

Reputation: 1240

Implicits via method/constructor arguments vs using implicitly

This code compiles and does exactly what one expects

class MyList[T](list: T)(implicit o: Ordering[T])

However this does not:

class MyList2[T](list: T) {
  val o = implicitly[Ordering[T]]
}

And I can't see why. In the first example when the class is being constructed the compiler will find the Ordering implicit because it will know the concrete type T. But in the second case it should also find the implicit since T will already be a concrete type.

Upvotes: 3

Views: 118

Answers (4)

Alexey Romanov
Alexey Romanov

Reputation: 170735

But in the second case it should also find the implicit since T will already be a concrete type.

Scala (and Java) generics don't work like C++ templates. The compiler isn't going to see MyList2[Int] elsewhere, generate

class MyList2_Int(list: Int) {
  val o = implicitly[Ordering[Int]]
}

and typecheck that definition. It is MyList2 itself which gets typechecked, with no concrete T.

Upvotes: 0

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

In the first example when the class is being constructed the compiler will find the Ordering implicit because it will know the concrete type T.

In the first example, one needs to have an implicit Ordering[T] in scope for the compiler to find it. The compiler by itself doesn't "make up" implicits. Since you've directly required one to be available via the second parameter list, if such an implicit exists, it will be passed down to the class constructor.

But in the second case it should also find the implicit since T will already be a concrete type.

The fact that T is a concrete type at compile time doesn't help the compiler find an implicit for it. When we say T is a concrete type, you must remember that at the call-site, T is simply a generic type parameter, nothing more. If you don't help the compiler, it can't give the guarantee of having an implicit in scope. You need to have the method supply an implicit, this can be done via a Context Bound:

class MyList2[T: Ordering](list: T)

Which requires the existence, at compile time, of an ordering for type T. Semantically, this is equivalent to your second parameter list.

Upvotes: 3

slouc
slouc

Reputation: 9698

You must always tell the compiler that an implicit should be provided for your type. That is, you must always put implicit o: Ordering[T]. What implicitly does is that it allows you to access the implicit in case you haven't named it. Note that you can use syntax sugar (called "context bound") for the implicit parameter, in which case implicitly becomes neccessary:

class MyList2[T : Ordering](list: T) {
  val o = implicitly[Ordering[T]]
}

Type [T : Ordering] is a shorthand for "some type T for which an implicit Ordering[T] exists in scope". It's the same as writing:

class MyList2[T](list: T)(implicit o: Ordering[T]) {

}

but in that case implicitly is not needed since you can access your implicit parameter by its identifier o.

Upvotes: 1

Jesper
Jesper

Reputation: 206816

For the second to work, you need to specify that there is an Ordering for type T using a context bound:

class MyList2[T : Ordering](list: T) {
  val o = implicitly[Ordering[T]]
}

Upvotes: -1

Related Questions