3

I want a trait Foo to provide transform method that would apply a function to it. Also, I want to force implementing classes to have an increment method that would somehow transform the object as well. Naive solution:

trait Foo {
  def transform(fun: Foo => Foo): Foo = fun(this)
  def increment(n: Int): Foo
}

case class A(a: Int) extends Foo {
  // expecting available: transform(fun: A => A): A
  // to be implemented: increment(n: Int): A
  ...
}

Above won't work... Inherited transform still expects Foo => Foo, not A => A and increment still wants to return Foo, not A.

One more attempt:

trait Foo {
  def transform[C <: Foo](fun: C => C): C = fun(this.asInstanceOf[C])
  def increment[C <: Foo](n: Int): C
}

case class A(a: Int) extends Foo {
  def increment(n: Int) = A(a + n)
}

A will not compile - it will still complain about the signature.

Taking out the increment function, transform works. However asInstanceOf looks a bit unsafe. Also, I need to explicitly provide type parameter to transform:

val a = A(1)
a.transform[A](x => x.copy(x.a + 1)) // returns A(2)

I wonder if there's a smart way to have it done.

4
  • Are you sure you're typing it in correctly? Can you give the specific errors you're getting? It seems to work fine for me: scalafiddle.io/sf/9gFk4sb/0 Commented Jul 11, 2019 at 19:39
  • 3
    This may help you. tpolecat.github.io/2015/04/29/f-bounds.html TL;DR; I would use a typeclass and maybe a sealed trait. Commented Jul 11, 2019 at 20:07
  • @Ethan - this won't work: scalafiddle.io/sf/fXI1Ser/0. Commented Jul 11, 2019 at 20:38
  • 1
    @LuisMiguelMejíaSuárez - thank you, it's long and I need to get deeper into it, but looks promising, like exactly what I need. Commented Jul 11, 2019 at 20:49

1 Answer 1

5

The most direct way of getting what you want is to move the type parameter up to the trait declaration. That gives trait Foo[C]{...}. However, using copy in your transform still won't work, since the Foo trait doesn't know anything about anything that extends it. You can give it a bit more information with self typing:

trait Foo[C] {
  this: C =>
    def transform(fun: C => C): C = fun(this)
    def increment(n: Int): C
}

case class A(a: Int) extends Foo[A] {
  def increment(n: Int) = A(a + n)
}

The A extends Foo[A] is a little awkward to use here, but it works, since now when you extend Foo, it provides that type information back to the trait. That's still a little bit awkward though. It turns out there's a technique called type classes that we can use here to potentially improve things. First, you set up your trait. In a type class, there is exactly one implementation of the trait per type, so each method should also take in the instance that you want to operate on:

trait Foo[C] {
  def transform(c: C)(f: C => C): C
  def increment(c: C, inc: Int): C
}

Next, in the companion object you set up instances of the typeclass for the types you care about:

case class A(a: Int)

object Foo {
  implicit val ATransform = new Foo[A] {
    def transform (base: A)(f: A => A) = f(base)
    def increment(base: A, inc: Int) = A(base.a+inc)
  }

  //Convenience function for finding the instance for a type.
  //With this, Foo[A] is equivalent to implicitly[Foo[A]]
  def apply[C](implicit foo: Foo[C]) = foo
}

Now we can use the type class as follows:

val b = A(3)
Foo[A].transform(b)(x=>x.copy(a=x.a+1)) //A(4)
Foo[A].increment(b,5) //A(8)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.