0

I have a trait definition that wraps side effects when instantiated like this:

trait MyTrait[F[_]] {
  def func1(param: Param): F[Param]
}

Where my Param is a case class which itself takes type parameters like this:

final case class Param[F[_]] {
  field1: F[String],
  field2: F[Int]
)

Now my question is, what does it mean if I change my trait method signature to the following:

trait MyTrait[F[_]] {
  def func1(param: Param[_]): F[Param[_]]
}

As you can see I'm having a wildcard everywhere I reference the Param case class. Is this a good approach? I do not want to tie my interface to a type expectation on a method param.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
joesan
  • 13,963
  • 27
  • 95
  • 232
  • 2
    `final case class Param[F[_]] { ... }` is not valid syntax. `F[Param]` is not a well-formed type for `Param: (* -> *) -> *` and `F: * -> *`. I don't understand what's being asked. What does _"Is this a good approach?"_ mean? Approach to what? Does the type express what you wanted to express? If it does, it's the right approach. If it doesn't, it's the wrong approach. Impossible to say without knowing what you're trying to express. – Andrey Tyukin Dec 07 '22 at 11:50

1 Answers1

4

As @AndreyTyukin noticed, your code doesn't compile because Param and F don't agree in kinds

trait MyTrait[F[_]] {
  def func1(param: Param): F[Param]
}
//compile error: class Param takes type parameters

final case class Param[F[_]](
  field1: F[String],
  field2: F[Int]
)

https://scastie.scala-lang.org/DmytroMitin/K2EHGDXERFCJisz45edMsA

Maybe you meant

trait MyTrait[F[_]] {
  def func1(param: Param[F]): F[Param[F]]
}

final case class Param[F[_]](
  field1: F[String],
  field2: F[Int]
)

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

func1 return type F[Param[F]] looks like fix point https://free.cofree.io/2017/11/13/recursion/

Now my question is, what does it mean if I change my trait method signature to the following:

trait MyTrait[F[_]] {
  def func1(param: Param[_]): F[Param[_]]
}

Instead of Param[F] with current effect F you're starting to use an existential type Param[_] with arbitrary (unknown) effect, possibly different from F.

What is an existential type?

Is this a good approach? I do not want to tie my interface to a type expectation on a method param.

Depends on your goal. Does it make sense for your setting that MyTrait and Param will have unconnected effects?

For example one of them is going to database while the other is writing to a file on disc. One of them is travelling through time while the other is launching missiles.

If this really makes sense for your setting to work with different effects, consider modifying the signature adding the 2nd effect type (rather than existential) on method level

trait MyTrait[F[_]] {
  def func1[G[_]](param: Param[G]): F[Param[G]]
}

(Or should it be still F[Param[F]]? Or G[Param[F]]? This depends on your setting.)

or on type-class level

// (*)
trait MyTrait[F[_], G[_]] {
  def func1(param: Param[G]): F[Param[G]]
}

Or you can even try

trait MyTrait[F[_]] {
  def func1[G[_], H[_]](param: Param[G]): F[Param[H]]
}

or

trait MyTrait[F[_], G[_], H[_]] {
  def func1(param: Param[G]): F[Param[H]]
}

After new question Type Arguments and Bounds in Scala I'll add here that one more option is to make G an abstract type member rather than method's type parameter. Then G must be implemented in inheritors rather than the method must work for arbitrary G.

trait MyTrait[F[_]] {
  type G[_]
  def func1(param: Param[G]): F[Param[G]]
}

It's similar to the above (*), i.e. having G a type parameter of the type-class.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 1
    Idk, I feel that `Param[_]` with a higher-kinded `_` is 99.99% a red flag, unless OP is secretly Miles Sabin or something... How often does one want to existentially quantify over effects? What meaningful statements, existentially quantified over effects, can even be made? _"One of them is travelling through time while the other is launching missiles"_ - sounds like an important choice, maybe too important to be left unspecified or buried in wildcards. – Andrey Tyukin Dec 07 '22 at 13:26
  • 1
    @AndreyTyukin Well, higher-kinded existentials doesn't threaten me https://github.com/apache/spark/pull/38740/files#diff-107dcb1ceee9ea1b4fe20125be412d89b60395b9d8ea67d70853ecd429b20a64 although maybe you're right. See the update with the 2nd effect type. – Dmytro Mitin Dec 07 '22 at 13:42
  • 1
    _"higher-kinded existentials doesn't threaten me"_ - I have no doubt that they don't threaten you, but when I look at the non-compiling code in the question, I feel like the situation might be slightly different for someone else I now feel like your answer has covered some kind of hypersphere in the space of all compilable solutions with the minimum Levenshtein distance to the code in the question, which, on the one hand, probably simplifies the life of the OP quite a bit, but on the other hand indicates that the question was somewhat of an XY-problem. – Andrey Tyukin Dec 07 '22 at 14:09
  • I did try to play around and here is a version that fails compilation: https://scastie.scala-lang.org/NvvgWEYMTKuDVXPfQgbRVQ – joesan Dec 07 '22 at 20:05
  • Any idea what I'm doing wrong? – joesan Dec 07 '22 at 20:07
  • And this one here fails as well https://scastie.scala-lang.org/35pqGtqnQIGvZpGl4BTlFg – joesan Dec 07 '22 at 20:15
  • @joesan `Option` in `def func[Option]...` is not `scala.Option`, it's the same as `def func[A]...`, you just called `A` `Option`, which shadows `scala.Option`. – Dmytro Mitin Dec 07 '22 at 20:43
  • @joesan https://stackoverflow.com/questions/54443888/what-causes-this-type-error-in-a-pattern-guard-for-matching-list-of-tuples https://stackoverflow.com/questions/56598220/strange-error-with-string-in-scala-2-12-7 https://stackoverflow.com/questions/57718070/type-parameters-applied-to-scala-function – Dmytro Mitin Dec 07 '22 at 20:47
  • @joesan 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. Use `-Xlint:type-parameter-shadow`. – Dmytro Mitin Dec 07 '22 at 20:52
  • Would you please show me how in the scastie example? – joesan Dec 07 '22 at 20:53
  • @joesan Show what? I have no idea what you're doing there. Just don't use `Option` for a new type parameter, use `A` etc. – Dmytro Mitin Dec 07 '22 at 20:57
  • @joesan if you still have a question feel free to start a new question. – Dmytro Mitin Dec 07 '22 at 23:10
  • 1
    https://stackoverflow.com/questions/74725562/type-arguments-and-bounds-in-scala – joesan Dec 08 '22 at 04:36
  • @joesan Answered there and updated here. – Dmytro Mitin Dec 08 '22 at 05:53