0

I have difficulties understanding Scalas type system. What's the correct way to write this code?

implicit class ExtSeq[A <: GenSeqLike[B, A], B](seq: A) {
  def prePadTo(len: Int, elem: B) = seq.reverse.padTo(len, elem).reverse
}
Tesseract
  • 8,049
  • 2
  • 20
  • 37

2 Answers2

4

Simplest way - just use Seq:

implicit class ExtSeq[T](seq: Seq[T]) {
  def prePadTo(len: Int, elem: T) = seq.reverse.padTo(len, elem).reverse
}

Generic way - use IsTraversableOnce:

import scala.collection.GenTraversableOnce
import scala.collection.generic.{IsTraversableOnce, CanBuildFrom}

class PrePadTo[T, Repr](coll: GenTraversableOnce[T]) {
  def prePadTo[That](len: Int, elem: T)
                    (implicit cbf: CanBuildFrom[Repr, T, That]): That = {
    val b = cbf()
    val tmp = coll.toSeq
    b ++= Iterator.fill(len - tmp.size)(elem)
    b ++= tmp.iterator
    b.result
  }
}

implicit def toPrePadTo[Repr](coll: Repr)
                             (implicit traversable: IsTraversableOnce[Repr]) =
    new PrePadTo[traversable.A, Repr](traversable.conversion(coll))

Usage:

scala> "abc".prePadTo(5, '-')
res0: String = --abc
senia
  • 37,745
  • 4
  • 88
  • 129
  • Thanks, but in this case the return type will always be Seq and not the type of the original collection. – Tesseract Dec 27 '13 at 15:55
  • @SpiderPig: see update. Generic way works even with types that are not `GenSeqLike` like `String`. – senia Dec 27 '13 at 16:16
  • Oh, good point, I always forget about `IsTraversableOnce`. Although the `prePadTo` implementation now looks uglier than the original, but I get it, you can't have your cake and eat it. – gourlaysama Dec 27 '13 at 16:18
  • That works great, but I noticed one small error. It has to be new PrePadTo[traversable.A, Repr](traversable.conversion(coll)) otherwise Vector('a', 'b', 'c').prePadTo(5, '-') would also return a string. – Tesseract Dec 27 '13 at 17:08
  • @senia - Question for you, can you explain how "That" gets processed and properly handled by the compiler? I don't see how "That" gets bound to a "String" in your example. – Ryan LeCompte Dec 27 '13 at 18:12
  • @RyanLeCompte: Let's suppose we have a method `def test[A, B](a: A)(implicit mt: MyTest[A,B])`. When scala gets `test(1)` it searches for `MyTest[Int, B]` (it don't know what is `B` yet). If there is only one implicit `MyTest` with first type parameter `Int` in scope - `MyTest[Int, String]` it's obvious the only possible value for `B` is `String`. If there is also `MyTest[Int, Any]` in scope you'll get the moste concrete one - `MyTest[Int, String]`. Same story with `CanBuilFrom`. – senia Dec 27 '13 at 19:36
  • 1
    @RyanLeCompte: see also [this great answer](http://stackoverflow.com/a/1716558/406435) by [Daniel C. Sobral](http://stackoverflow.com/users/53013/daniel-c-sobral) (the best answer about scala collections ever!). – senia Dec 27 '13 at 19:39
1

There are two issues with your implicit class:

  • B isn't constrained, so it will end up as Nothing. You need a way to infer it from the input collection.

  • You need an implicit CanBuildFrom on your prePadTo method so that the padTo method knows how to build a new instance of the input collection.

Hence:

import scala.collection.{GenSeq, GenSeqLike}
import scala.collection.generic.CanBuildFrom

implicit class ExtSeq[A <: GenSeqLike[B, A], B](seq: A with GenSeq[B]) {
  def prePadTo(len: Int, elem: B)(implicit cbf: CanBuildFrom[A, B, A]) =
    seq.reverse.padTo(len, elem).reverse
}

And then:

scala> List(1,2,3).prePadTo(10, 4)
res1: List[Int] = List(4, 4, 4, 4, 4, 4, 4, 1, 2, 3)

scala> Vector('1','2','3').prePadTo(10, '4')
res2: Vector[Char] = Vector(4, 4, 4, 4, 4, 4, 4, 1, 2, 3)

Note that it will not work for classes that are only implicit convertible to a Seq, like String for example.

gourlaysama
  • 11,240
  • 3
  • 44
  • 51
  • Why is the compiler not able to infer B? If I write e.g. new ExtSeq(Vector(1,2,3)), the compiler should be able to tell that Vector(1,2,3) extends GenSeqLike[Int, Vector[Int]] and therefore B would be Int. Why is it necessary to put "with GenSeq[B]" in there? – Tesseract Dec 27 '13 at 17:10
  • @SpiderPig because a type constraint like `A <: GenSeqLike[B, A]` only constrains what is on the left-hand side of `<:`, i.e. only `A`. `B` just happens to be referenced in that constraint. Said another way, `[A <: GenSeqLike[B, A], B]` means "whatever `B` ends up being, `A` has to be a `GenSeqLike[B, A]` *and* B can be anything". `A` is then inferred from the parameter, `B` doesn't appear in the parameter, so it becomes `Nothing`, and since a `GenSeqLike[B, A]` is a `GenSeqLike[Nothing, A]`, the constraint is satisfied. – gourlaysama Dec 27 '13 at 20:07