0

I have the following example:

import scala.concurrent.Future

trait MyTrait[F[_]] {

  case class Test[X[_]](x: X[Int])

  def test[G[_]]: F[Test[G]]

}
class LocImpl extends MyTrait[Future] {

  import scala.concurrent.ExecutionContext.Implicits.global

  def test[Option]: Future[Test[Option]] = {
    Future { new Test[Option](Option(1)) }
  }
}

Which fails compilation due to the reason that:

Type argument Option does not have the same kind as its bound [_$2]

I'm binding the generic type on the test function to Option and binding the trait to Future. So what is the problem here?

https://scastie.scala-lang.org/35pqGtqnQIGvZpGl4BTlFg

joesan
  • 13,963
  • 27
  • 95
  • 232

1 Answers1

2

For the reference, the beginning was here: Scala Higher Kinded Types for Traits and Method Parameters

Option in def func[Option]... is not scala.Option, you're just defining a new type parameter calling it Option, it's the same as def func[A]..., you just called A Option, which shadows scala.Option.

This is a difference def func[A] (definition site) vs. func[A] (call site) i.e. whether A is a new type or known type.

It's better to use scalacOptions += "-Xlint:type-parameter-shadow" to avoid shadowing known types.

What causes this type error in a pattern guard for matching list of tuples

Strange Error with String in Scala 2.12.7

Type parameters applied to Scala Function

And since def test[Option]... is the same as def test[A]..., errors are understandable. Test[Option] aka Test[A] doesn't make sense because of a disagreement in kind. Option(1) aka A(1) doesn't make sense either.

So it should be just

def test: Future[Test[Option]] = {
  Future { new Test[Option](Option(1)) }
}

without type parameter. But then you break overriding. You seem to want having G an abstract type (to be implemented in inheritors) rather than method's type parameter (when the method must be defined for arbitrary G)

trait MyTrait[F[_]] {

  case class Test[X[_]](x: X[Int])

  type G[_]

  def test: F[Test[G]]

}
class LocImpl extends MyTrait[Future] {

  import scala.concurrent.ExecutionContext.Implicits.global

  type G[A] = Option[A]

  def test: Future[Test[Option]] = {
    Future { new Test[Option](Option(1)) }
  }
}

https://scastie.scala-lang.org/DmytroMitin/rk82W02DQOiFAJ7mghHcAQ/1

It's similar to having G a type parameter of the type class

trait MyTrait[F[_], G[_]] {

  case class Test[X[_]](x: X[Int])

  def test: F[Test[G]]

}
class LocImpl extends MyTrait[Future, Option] {

  import scala.concurrent.ExecutionContext.Implicits.global

  def test: Future[Test[Option]] = {
    Future { new Test[Option](Option(1)) }
  }
}

https://scastie.scala-lang.org/DmytroMitin/rk82W02DQOiFAJ7mghHcAQ/2

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Having both the types on the trait is not what I want. I want the trait to be dependant only on F and the method param is the only one that determines the type G. – joesan Dec 08 '22 at 17:27
  • 1
    @joesan Well, in some sense both times (when `G` is a type parameter of `MyTrait` and when it's a type member) `MyTrait` is "dependant" both on `F` and `G`. Just in one case you specify the type `MyTrait[Future, Option]` while in another `MyTrait[Future] { type G[A] = Option[A] }` aka `MyTrait.Aux[Future, Option]` if you introduce `Aux`-type. If you really need to ignore `G` then you have existential `MyTrait[Future, _]` in the 1st case and `MyTrait[Future]` aka `MyTrait.Aux[Future, _]` in the 2nd. – Dmytro Mitin Dec 08 '22 at 17:51
  • The reason why I do not want the method param type on the trait is because of the fact that I have two functions in this trait which take the same parameter, but one of the parameter has an Option applied to all its fields, while the other has Id applied to all its fields. So if I have the param's generic type on the trait, then I should have two separate implementations of this trait just to satisfy these two methods. – joesan Dec 08 '22 at 18:01
  • @joesan Not sure I understand. It's hard to discuss without code. Anyway if a type member works for you the better. – Dmytro Mitin Dec 08 '22 at 18:45
  • I have tried to explain it better here: https://scastie.scala-lang.org/RloFroGyQgCVZYHGE2PMXw – joesan Dec 08 '22 at 20:36
  • I thank you very much for this excellent help that you have been offering so far. I'm understanding a lot this way. – joesan Dec 08 '22 at 20:37
  • 1
    @joesan Thanks for details but it's still not clear for me why you can't have two or three or as many as you need type parameters of `MyTrait`. They can be type parameters https://scastie.scala-lang.org/DmytroMitin/ShB62CPYSVCJQ253zjE3jQ/2 or type members https://scastie.scala-lang.org/DmytroMitin/ShB62CPYSVCJQ253zjE3jQ/3 – Dmytro Mitin Dec 09 '22 at 00:25
  • @joesan I refer to *"I do not want the method param type on the trait is because of the fact that I have two functions in this trait which take the same parameter, but one of the parameter has an Option applied to all its fields, while the other has Id applied to all its fields. So if I have the param's generic type on the trait, then I should have two separate implementations of this trait just to satisfy these two methods."* – Dmytro Mitin Dec 09 '22 at 00:25
  • 1
    @joesan By the way, in the 2nd case (with type members) by `Aux`-type I meant https://scastie.scala-lang.org/DmytroMitin/ShB62CPYSVCJQ253zjE3jQ/5 – Dmytro Mitin Dec 09 '22 at 00:33
  • Ok, now I'm getting it with the Aux type. Seems reasonable to me. – joesan Dec 09 '22 at 05:15
  • @joesan `Aux` pattern is common for example in type-level programming https://stackoverflow.com/questions/43900674/understanding-the-aux-pattern-in-scala-type-system https://stackoverflow.com/questions/38541271/what-does-the-aux-pattern-accomplish-in-scala https://stackoverflow.com/questions/50977833/aux-pattern-workaround https://stackoverflow.com/questions/65838535/in-scala-how-to-make-type-class-working-for-aux-pattern https://gigiigig.github.io/posts/2015/09/13/aux-pattern.html https://www.signifytechnology.com/blog/2019/02/scala-the-aux-pattern-and-path-dependent-types-by-basement-crowd – Dmytro Mitin Dec 09 '22 at 08:49
  • @joesan Just you have higher-kinded `Aux`. – Dmytro Mitin Dec 09 '22 at 08:49
  • @joesan Couple of comments more. Can `LocImpl` be an object rather than class? Currently you seem not to need different its instances. Can `case class Test[X[_]]...` be not nested into `MyTrait`? `X[_]` is independent of `F[_]` and `Test[X]` seems to be independent of an instance of `MyTrait[F]`. Also maybe you can make `MyTrait` a *type class* i.e. its implementations implicit: `implicit object LocImpl extends MyTrait[Future]...` `def foo[F[_]](implicit inst: MyTrait[F])...` (using `inst.test()`, `inst.test1()`, `inst.G[A]`, `inst.H[A]`...) – Dmytro Mitin Dec 09 '22 at 09:08
  • 1
    @joesan Intros to type classes: https://kubuszok.com/2018/implicits-type-classes-and-extension-methods-part-1/ https://tpolecat.github.io/2013/10/12/typeclass.html https://tpolecat.github.io/2015/04/29/f-bounds.html https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:generic:type-classes (chapter 3.1) https://www.baeldung.com/scala/type-classes https://docs.scala-lang.org/scala3/book/types-type-classes.html – Dmytro Mitin Dec 09 '22 at 09:08
  • 1
    @joesan Also please look at *tagless final* approach (higher-kinded type classes) https://www.signifytechnology.com/blog/2019/02/an-introduction-to-tagless-final-in-scala-by-basement-crowd https://scalac.io/blog/tagless-final-pattern-for-scala-code/ https://www.baeldung.com/scala/tagless-final-pattern https://blog.rockthejvm.com/tagless-final/ – Dmytro Mitin Dec 09 '22 at 09:08
  • @joesan Maybe you could also have an inner trait / type class https://scastie.scala-lang.org/DmytroMitin/ShB62CPYSVCJQ253zjE3jQ/7 – Dmytro Mitin Dec 15 '22 at 15:33