Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reference parameter of a higher kinded type parameteter?

Tags:

generics

scala

Suppose you have a trait like this:

trait Foo[A]{
    def foo: A
}

I want to create a function like this:

def getFoo[A <: Foo[_]](a: A) = a.foo

The Scala Compiler deduces Any for the return type of this function. How can I reference the anonymous parameter _ in the signature (or body) of getFoo? In other words, how can I un-anonymize the parameter?

I want to be able to use the function like

object ConcreteFoo extends Foo[String] {
  override def foo: String = "hello"
}

val x : String = getFoo(ConcreteFoo)

which fails compilation for obvious reasons, because getFoo is implicitly declared as Any.

If this is not possible with Scala (2.12 for that matter), I'd be interested in the rational or the technical reason for this limitation. I am sure there are articles and existing questions about this, but I appear to be lacking the correct search terms.


Update: The existing answer accurately answers my question as stated, but I suppose I wasn't accurate enough regarding my actual usecase. Sorry for the confusion. I want to be able to write

def getFoo[A <: Foo[_]] = (a: A) => a.foo

val f = getFoo[ConcreteFoo.type]

//In some other, unrelated place
val x = f(ConcreteFoo)

Because I don't have a parameter of type A, the compiler can't deduce the parameters R and A if I do

def getFoo[R, A <: Foo[R]]: (A => R) = (a: A) => a.foo

like suggested. I would like to avoid manually having to supply the type parameter R (String in this case), because it feels redundant.

like image 700
LukeG Avatar asked Apr 24 '19 10:04

LukeG


1 Answers

To answer literally your exact question:

def getFoo[R, A <: Foo[R]](a: A): R = a.foo

But since you don't make any use of the type A, you can actually omit it and the <: Foo[..] bound completely, retaining only the return type:

def getFoo[R](a: Foo[R]): R = a.foo

Update (the question has been changed quite significantly)

You could smuggle in an additional apply invocation that infers the return type from a separate implicit return type witness:

trait Foo[A] { def foo: A }

/** This is the thing that remembers the actual return type of `foo`
  * for a given `A <: Foo[R]`.
  */
trait RetWitness[A, R] extends (A => R)

/** This is just syntactic sugar to hide an additional `apply[R]()`
  * invocation that infers the actual return type `R`, so you don't
  * have to type it in manually.
  */
class RetWitnessConstructor[A] {
  def apply[R]()(implicit w: RetWitness[A, R]): A => R = w
}

def getFoo[A <: Foo[_]] = new RetWitnessConstructor[A]

Now it looks almost like what you wanted, but you have to provide the implicit, and you have to call getFoo[ConcreteFoo.type]() with additional pair of round parens:

object ConcreteFoo extends Foo[String] {
  override def foo: String = "hey"
}

implicit val cfrw = new RetWitness[ConcreteFoo.type, String] {
  def apply(c: ConcreteFoo.type): String = c.foo
}

val f = getFoo[ConcreteFoo.type]()
val x: String = f(ConcreteFoo)

I'm not sure whether it's really worth it, it's not necessarily the most straightforward thing to do. Type-level computations with implicits, hidden behind somewhat subtle syntactic sugar: that might be too much magic hidden behind those two parens (). Unless you expect that the return type of foo will change very often, it might be easier to just add a second generic argument to getFoo, and write out the return type explicitly.

like image 150
Andrey Tyukin Avatar answered Nov 07 '22 22:11

Andrey Tyukin