Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala .zip with higher-kinded types, weird behaviour

Following the question and answer in Scala methods and higher-kinded type parameters where I asked how to create a method with higher-kinded parameters so I could use any Seq, I am facing now a weird problem when using the .zip method. The code below doesn't compile, the complire reports:

Error:(18, 28) type mismatch;
 found   : Seq[X]
 required: S[X]
        itrA.zip(itrB).map {

I don't know where S[X] is transformed to Seq[X]. However, if I replace the code with the call to .zip with a more simple one (the commented one) which simply sums both heads of itrA and itrB and returns ItrA, the compilation is successful.

The code is:

object SeqOps {

  def sum[X: Numeric, S[Y] <: Seq[Y]](s: Seq[S[X]])
    (implicit cbf: CanBuildFrom[Nothing, X, S[X]]): Seq[X] =

  /*
      This code compiles
       s.reduce{(itrA, itrB) =>
          val num = implicitly[Numeric[X]]
          val sum = new num.Ops(itrA.head).+(itrB.head)
          itrA
          */

      s.reduce{(itrA, itrB) =>
        itrA.zip(itrB).map { // it seems that itrB loses the type here :/
          case (a, b) =>
            val num = implicitly[Numeric[X]]
            val sum = new num.Ops(a).+(b)
            sum
      }

  }

  def main(args: Array[String]): Unit = {
    sum(Seq(Vector(1), Vector(1)))
  }

}

The questions are: Why is this happening? How can I fix it?

like image 448
vicaba Avatar asked Jan 16 '26 23:01

vicaba


1 Answers

It doesn't know that if you zip two S[X] that it can somehow build an S[(X, X)] out of it. For this to work, you need a CBF that knows how to build an S[(X, X)].

Unfortunately, there don't seem to be any implicits that would be able to build e.g. Vector[Int] out of a generic Seq[_], so you need more information about the type of the collection from which you're building. The idea is that traits with -Like name can provide this information. In this case, SeqLike[X, Repr] carries the Repr-type around.

If you now stare long enough at the signature of zip in SeqLike:

def zip
  [A1 >: A, B, That]
  (that: GenIterable[B])
  (implicit bf: CanBuildFrom[Repr, (A1, B), That])
: That

then you'll see that you can give it a CBF that has Repr in the first component. So you might try something like this (note with SeqLike and two CBFs - one for zip, another one for map):

import scala.collection.generic.CanBuildFrom
import scala.language.higherKinds
import scala.collection.SeqLike

object SeqOps {


  def sum[X: Numeric, S[Y] <: Seq[Y] with SeqLike[Y, S[Y]]]
    (s: Seq[S[X]])
    (implicit 
      cbf1: CanBuildFrom[S[_], X, S[X]],
      cbf2: CanBuildFrom[S[_], (X, X), S[(X, X)]]
    )
  : Seq[X] = {
    val num = implicitly[Numeric[X]]
    import num._
    s.reduce(_.zip(_)(cbf2).map{ case (a, b) => a + b })
  }


  def main(args: Array[String]): Unit = {
    println(sum(Seq(Vector(1), Vector(1))))     // Vector(2)
    println(sum(Seq(List(2, 3), List(4, 5))))   // List(6, 8)
  }

}

Another remark: In certain cases, it is easier to think of Nothing as of Unobtanium. If your CBF requires Unobtanium to build an S[X], it's probably not good for anything, because where do you want to get Unobtanium?

like image 144
Andrey Tyukin Avatar answered Jan 19 '26 17:01

Andrey Tyukin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!