0

I am working with Scala's implicit class mechanism and can't get java.lang.String recognized as Iterable[A].

implicit class PStream[E](stream: Iterable[E]) {
    def splitOn(test: Iterable[E] => Boolean): Option[(Iterable[E], Iterable[E])] = {
        ???
    }
}

With just the above definition, IntelliJ and SBT state that ...

Error:(31, 8) value splitOn is not a member of String possible cause: maybe a semicolon is missing before `value splitOn'? .splitOn(s => Character.isWhitespace(s.head))

... when I try this ...

            line: String =>
                val Some((identifier: String, patternn: String)) =
                    line
                        // take the identifier
                        .splitOn(s => Character.isWhitespace(s.head))
pal
  • 942
  • 1
  • 9
  • 19
  • That was a little bit unexpectedly quick accept. I've added a workaround proposal, just in case you are interested. The idea is to use implicit defs that require other implicit parameters that look more like typeclasses instead of implicit conversions. Those things tend to compose more nicely, and behave a bit more predictably. – Andrey Tyukin Mar 23 '18 at 15:35

2 Answers2

5

That's because Iterable[E] is a Scala trait, and as such a relatively "recent" invention, whereas java.lang.String is a fundamental Java datatype that has been there since version 1.0. from 1995. Obviously, java.lang.String does not implement Iterable[E].

Moreover, even if there is an implicit conversion from String to Iterable[E], Scala will never attempt more than one implicit conversion on a single expression.

If you want to pimp String, you have to pass String as a single parameter to your implicit class. The compiler will refuse to build crazy towers of multiple implicit conversions, because it would make the compilation time inacceptable otherwise.


What you could try instead would be something like this:

implicit def toPStreamOps[X, E](x: X)(implicit iter: IsIterable[X, E])
: PStream[X, E] = ???

and then provide a separate

implicit object StringIsIterableChar 
extends IsIterable[String, Char] {
  def asIterable(s: String): Iterable[Char] = ???
}

This would give you pretty much the same functionality, but it would not require an uncontrolled explosion of implicit conversions.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • "Scala will never attempt more than one implicit conversion on a single expression." is what I needed to hear. – pal Aug 17 '18 at 12:13
  • @pal Note that it does not mean that you cannot [chain implicits (FAQ article)](https://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html). – Andrey Tyukin Aug 17 '18 at 12:14
0

String (and also Array[T]) are inherited from Java and thus don't extend Scala's Iterable. In Scala though, there are implicit wrappers for String and Array[T] into objects that extend IndexedSeq and accordingly Iterable.

The original interface to request an implicit conversion of an argument was a view bound:

implicit class PStream[E, T <% Iterable[E]](stream: T) { 
  /* ... */
}

It is deprecated now, but you can just request the implicit conversion as an implicit argument:

implicit class PStream[E, T](stream: T)(implicit toIterable: T => Iterable[E]) { 
  /* ... */
}

This code will support normal Iterables, and also Strings and Arrays.

Kolmar
  • 14,086
  • 1
  • 22
  • 25