3

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Simão Martins
  • 1,210
  • 11
  • 22

4 Answers4

3

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.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • One does **not** need to have an implicit `Ordering[T]` is scope, this code is perfectly valid `val ord = implicitly[Ordering[Int]]` and it works. It works because an `Ordering` for `Int` is declared implicitly in the standard lib. Since in `MyList2` constructor body the type `T` is known the compiler should be able to find an implicit for it and only if it cannot should it complain. Had I done `new MyList2(5)` the compiler would not complain. – Simão Martins Oct 13 '16 at 13:51
  • 1
    @SimãoMartins But why do you think `implicitly[Ordering[Int]]` works? It's because the compiler is able to find an implicit in scope. A generalization for type `T` isn't the same, what is type `T` for the compiler at the call site? The compiler needs to be able to infer what `T` is in order to provide an implicit for it. – Yuval Itzchakov Oct 13 '16 at 13:55
  • The call site is inside the body of the constructor, which will only be invoked when the constructor is called and at that time the compiler has already inferred the type `T`. So **only** when the constructor is called will the compiler need to find an implicit for `Ordering[T]` and since at that time `T` is already a concrete type it should have no problem. Of course such an `Ordering` might not exist because I could have declared `T` to be a `File` for example, but I could also have declared `T` to be an `Int`. But the compiler is saying that it can't find the implicit no matter what. – Simão Martins Oct 13 '16 at 14:08
  • 2
    *So only when the constructor is called will the compiler need to find an implicit for Ordering[T] and since at that time T is already a concrete type it should have no problem.* But that's incorrect. When the compiler compiles `MyList2[T]`, there may not be a concrete type at compile time. Imagine you only create this as a public API, containing no concrete implementations in your code, what is `T` then to the compiler? The compiler needs to guarantee that for any type `T` there will be an implicit in scope for it to construct `val o`. – Yuval Itzchakov Oct 13 '16 at 14:21
1

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.

slouc
  • 9,508
  • 3
  • 16
  • 41
0

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.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
-1

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]]
}
Community
  • 1
  • 1
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • Why the downvote? Please explain, so that I can learn if there's anything wrong with my answer and improve it. – Jesper Jan 19 '19 at 13:41