7

I have the following extension class that adds a myAppend method to anything SeqLike.

implicit class WithAppend[A, R](s: SeqLike[A, R]) extends AnyVal {
  def myAppend(i: A)(implicit cbf: CanBuildFrom[R, A, R]): R = s :+ i
}

How can I port this code to Scala 2.13 and retain similar performance characteristics? Bonus points if the extended class can remain an AnyVal

Few things I have tried:

class Extends1[R, S <: IsSeq[R]](c: R, isSeq: S) {
  def myAppend(a: isSeq.A): R = (isSeq(c) :+ a).asInstanceOf[R]
}

But the asInstanceOf is disappointing - is it even safe?

I could do:

class Extends3[S[_], A](c: SeqOps[A, S, S[A]]) {
  def myAppend(a: A): S[A] = c :+ a
}

but now we're constrained to collections of the form S[A] while the Scala 2.12 code can take any R.

Fernando Rezk
  • 314
  • 1
  • 18
thesamet
  • 6,382
  • 2
  • 31
  • 42
  • The only difference of `myAppend` from `:+` seems to be that it's only available when the return type is the same as the original sequence. So `asInstanceOf` is not so much disappointing as breaking the only usecase... – Alexey Romanov Mar 18 '19 at 05:46
  • 1
    It's probably impossible for anyone else to answer this since you've not said what you're trying to accomplish. It's likely `[A, S[_]](val s: SeqLike[A, S, S[A]])` and skipping the `CanBuildFrom` would work just fine, but who knows, since we have to guess why you want `myAppend` instead of just `:+` to start with. – Travis Brown Mar 18 '19 at 08:30
  • @TravisBrown More context: I am working on porting/improving the lenses functionality of ScalaPB. I want to provide an extension method for fields that are `Seq`-ish, that provides a modification of the field: https://github.com/scalapb/ScalaPB/blob/97780a92460c86a2a1671582bd4e65ded4cb0487/lenses/shared/src/main/scala/scalapb/lenses/Lenses.scala#L88. I edited the question to clarify my requirements: is the `asInstanceOf` safe? Can we operate on any `R` rather than on `S[A]`s ? – thesamet Mar 18 '19 at 15:38
  • are you using https://docs.scala-lang.org/overviews/core/custom-collection-operations.html as your guide? it only went up on the site recently – Seth Tisue Mar 18 '19 at 15:47
  • @SethTisue - yes, it was a great read. However, I've had some issue in implementing the ideas there in this context: If I get both an `IsSeq` and `BuildFrom` how can I require that they have the same `A`? The `BuildFrom.fromSpecific` has a `from` parameter that seem to be unused - it's unclear how to use `BuildFrom` here in a way that would have similar performance to calling `:+` – thesamet Mar 18 '19 at 16:25

1 Answers1

2

I can come up with a method signature and implementation that works without casts:

implicit class WithAppend[Repr](private val repr: Repr) extends AnyVal { 
  def myAppend[A0, C](a: A0)(
    implicit 
    isSeq: IsSeq[Repr]{ type A = A0 }, 
    b: BuildFrom[Repr, A0, C]
  ): C = b.fromSpecific(repr)(isSeq(repr) :+ a) 
}

It's hard to give guarantees about the performance of this, but I doubt it would be significantly worse than what you used to have. An alternative is b.newBuilder(repr).addAll(isSeq(repr)).addOne(a).result().

scala> List(1,2).myAppend(3)
res3: List[Int] = List(1, 2, 3)

scala> Vector(1,2).myAppend(3)
res4: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)

scala> "Strin".myAppend('g')
res5: String = String
Jasper-M
  • 14,966
  • 2
  • 26
  • 37
  • Thanks! Though it's a shame that myAppend has type parameters where all of the types are available at the implicit class scope. Is it possible to move them there? I wonder if the new collections should offer an `Aux` pattern for `IsSeq` and friends to simplify use cases like this. – thesamet Mar 18 '19 at 17:10
  • If you want it to be a value class it will be close to impossible. And I tried to convert it to a class with 2 parameters and an implicit def like in https://docs.scala-lang.org/overviews/core/custom-collection-operations.html but I couldn't immediately get it to work. But I would think it should be possible. – Jasper-M Mar 18 '19 at 17:16