pm42
pm42

Reputation: 19

How to have one covariant parameter in a class as well as a function to transform it?

I have a very simple class:

case class Foo[+T](t: T)

Now I want to add a argument to transform T to Int. For specific reasons, I do not want to use a typeclass, an implicit or any inheritance based solution because this is really what I am modeling: a class containing some data and the function to transform it.

So I write

case class Foo[+T](t: T, f: T => Int)

or

case class Foo[+T](t: T, f: Function1[T, Int])

Of course, it doesn't work because f is contravariant on T. Any solution?

Upvotes: 0

Views: 97

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

You could try existential type

case class Foo[+T](t: T, f: (_ <: T) => Int)

but actually (_ <: T) => Int is just Nothing => Int.

(In Dotty it's also possible to have case class Foo[+T](t: T, f: [U <: T] => U => Int).)

Consider adding one more type parameter

case class Foo[+T, U <: T](t: T, f: U => Int)

Then you can use "partially applied" pattern when you want U to be inferred

def mkFoo[T] = new PartiallyApplied[T]
class PartiallyApplied[T] {
  def apply[U <: T](t: T, f: U => Int) = Foo(t, f)
}

trait Parent
case class Child(i: Int) extends Parent

mkFoo[Parent](new Parent {}, (c: Child) => c.i)

One more option is to make U a type member

trait Foo[+T] {
  type U <: T
  val t: T
  val f: U => Int
}

object Foo {
  def apply[T, _U <: T](_t: T, _f: _U => Int): Foo[T] { type U = _U } = new Foo[T] {
    override type U = _U
    override val t: T = _t
    override val f: U => Int = _f
  }

  def unapply[T](foo: Foo[T]): Option[(T, foo.U => Int)] = Some((foo.t, foo.f))
}

Maybe your class can be

case class Foo[+T](t: T) { 
  def f[U >: T](t1: U): Int = ??? 
}

Otherwise it's just invariant case class Foo[T](t: T, f: T => Int).

Upvotes: 2

Related Questions