Jeff
Jeff

Reputation: 1007

Generic container type (higher kind) for "map" in scala

I am trying to define a case class that is parameterized with a container type (higher kind). This container type can be anything as long as it has a map method defined.

I would like to achieve this result suggested by the following code:

import scala.language.higherKinds

case class Test[A, C[A]](init: A, trans: Map[A,C[A]]) {
  def convert[B](conv: A => B): Test[B, C[B]] = {
    val _init = conv(init)
    val _trans = trans map {case (k,v) => (conv(k) -> (v map {x => conv(x)})}
    Test(_init, _trans)
  }
}

the problem lies in the v map {x => conv(x)} part of the code. Since no bound was defined for C[A] it, obviously, does not compile.

The thing is that this container type can be an Id (Scalaz style, but with map instead of |>), and Option, or a collection (Seq, Set, List, etc.)

Is there any way to tell the scala compiler that the container type will must have the map method?

Upvotes: 0

Views: 362

Answers (1)

The best way to achieve what you want is by using the Functor type class. For example here is an example using Cats.
(You could do the same using Scalaz).

import scala.language.higherKinds

import cats.Functor
import cats.syntax.functor.toFunctorOps

final case class Test[A, C[_]](init: A, trans: Map[A,C[A]])(implicit CFunctor: Functor[C]) {
  def convert[B](conv: A => B): Test[B, C] = {
    val _init = conv(init)
    val _trans = trans map { case (k,v) => conv(k) -> v.map(x => conv(x)) }
    Test(_init, _trans)
  }
}

However, if for some reason you can't or don't want to use Cats/Scalaz, you could try Structural Types.

import scala.language.higherKinds
import scala.language.reflectiveCalls

final case class Test[A, C[A] <: { def map[B](f: A => B): C[B]}](init: A, trans: Map[A,C[A]]){
  def convert[B](conv: A => B): Test[B, C] = {
    val _init = conv(init)
    val _trans = trans map { case (k,v) => conv(k) -> v.map(x => conv(x)) }
    Test(_init, _trans)
  }
}

Nevertheless, note that the last one will work for Option, but will fail for List, simple because the map method in List receives an implicit CanBuildFrom, and for that reason it isn't equals to the one you want.

Upvotes: 3

Related Questions