4

I have a third party Java API with methods like this (simplified) :

public <C extends Comparable<C>> C jFoo(C c);

I want to write a wrapper like this:

def foo[C <: Comparable[C]](c: C): C = jFoo(c)

For allowing the wrapper method to accept primitives (since, to my understanding, boxing will happen anyway so that's not relevant), I want to specialize C:

def foo[@specialized(Int, Double) C <: Comparable[C]](c: C): C = jFoo(c)

However, scalac complains that, due to the upper bound on the parameter, the method cannot be specialized.

A solution would be to add an evidence parameter:

def foo[@specialized(Int, Double) C, CC <: Comparable[CC]](c: C)
                                                 (implicit evComp: C => CC): CC =
    jFoo(evComp(c))

This works, and you can now call:

foo(2.0)

However, the method signature - part of the API - is not really readable now.

Is there an alternative approach at the method level that would produce an equivalent, more readable signature, while maintaining specialization?

(Scala 2.12.x is fine, Dotty not so much)

mikołak
  • 9,605
  • 1
  • 48
  • 70
  • @YuvalItzchakov : the problem is that `Comparable` is not designed as a typeclass - it's designed to store state, you won't be able to use it on any given `C`. Also, note that you need `C <: Comparable[C]`, *not* `Comparable[C]` (unfortunately this is not equivalent). – mikołak May 16 '18 at 14:04
  • Yeah, it also doesn't actually match the type constraint. – Yuval Itzchakov May 16 '18 at 14:05
  • What is `evComp` in the penultimate code snippet, actually? – Andrey Tyukin May 16 '18 at 15:09
  • @AndreyTyukin: sorry, typo, corrected. This implicit is supplied by Scala's stdlib BTW, at least for Doubles (but I'm assuming for other primitives as well). – mikołak May 16 '18 at 15:23
  • Does the last code snippet `foo(2.0)` really compile? I for some reason get `error: type arguments [Double,Any] do not conform to method foo's type parameter bounds [C,CC <: Comparable[CC]]`. It looks as if it cannot infer the `java.lang.Double` as second argument. – Andrey Tyukin May 16 '18 at 15:35
  • @AndreyTyukin: you are again correct, this is the one time I foolishly assumed the program can be reduced to a specific minimal form without actually compiling it. Turns out scalac needs at least the return type to be bound to the input for the inference to work correctly. Corrected, and apologies for taking more of your time than necessary. – mikołak May 16 '18 at 18:20

1 Answers1

2

You could get rid of the second type parameter CC by making it a type member of a special EvC type class.

The following works:

import java.lang.Comparable

def jFoo[C <: Comparable[C]](c: C): Unit = println("compiles => ship it")

trait EvC[C] {
  type CC <: Comparable[CC]
  def apply(c: C): CC
}

def foo[@specialized(Int, Double) C](c: C)(implicit evC: EvC[C]): Unit = 
  jFoo[evC.CC](evC(c))

implicit object DoubleEvC extends EvC[Double] {
  type CC = java.lang.Double
  def apply(c: Double) = (c: java.lang.Double)
}

foo(42.0) // compiles => ship it

In a sense, I've just glued together the C => CC part with the type argument CC itself, so that the type CC does no longer appear in the type parameter list. I'm not sure whether you can get rid of all of that completely, it's still a fact that the built-in primitive types do not implement java.lang.Comparable.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • Arghh, I still keep forgetting about dependent types on occasion. That looks legit, thanks, I'll test it tomorrow (also, I'm holding out for other answers). Re last sentence - yeah, like i said, I only need it so that I don't have to coerce to `java.lang.Double` (the actual signature is a bit more elaborate, and scalac can't handle automatic conversion there), the boxing will happen anyway. I guess this is the problem with the original API, but I need a solution regardless. Thanks again. – mikołak May 16 '18 at 15:26
  • I'm getting some weird compiler errors in my specific case, but I'm accepting this answer anyway since, as far as the problem is defined, it works. – mikołak May 17 '18 at 16:10
  • @mikołak Wait, does my code produce compiler errors? That shouldn't be. In which version of the compiler does this happen? – Andrey Tyukin May 17 '18 at 16:12
  • Your code is fine - and that's why I've accepted the answer :). What produces errors is the application of the solution to the *original* problem (of which this question is the *generalization*). Since the error is weird and sounding like a compiler bug (in line of "type `X` required, but `X` found"), and I can't isolate the issue, *and* yours is a good answer to the generalized problem, all is fine as far this QA is concerned. I may revisit the specific compiler error in a later question. – mikołak May 17 '18 at 16:21
  • @mikołak It's a failure that occurs frequently when scala typer attempts to infer the weird `? extends Foo`-types in the Java classes, it's usually easily solvable by adding some type arguments explicitly. Any chance that it's something like "type Double required, but Double found", i.e. specifically Int or Double? Wait, I'll find a related example... – Andrey Tyukin May 17 '18 at 16:24
  • @mikołak Take a look at [this here](https://stackoverflow.com/questions/48892516/compiler-error-when-using-google-guava-from-scala-code/48893292#48893292). There is no way in hell you'd find it by google search, but I'm almost sure that it's a "bug" of this sort. – Andrey Tyukin May 17 '18 at 16:26
  • doesn't look like the same error, since the types in question are non-primitive (this is something like `Foo[evC.CC]`, so that would be a `java.lang.Double` in this case). I suspect it's more of a "dependent-types-in-return-signature" issue. Once I have time to revisit the problem, I'll probably ask a separate question. Thanks for the help in any case. – mikołak May 18 '18 at 07:19
  • 1
    And, funnily enough, today, with a new from-scratch rewrite, it works. Must have screwed up something with ascription and confused the compiler (one other possibility is that I've added some type aliases to the Java's API types, and this may have helped). – mikołak May 18 '18 at 07:27