I'm having some trouble understanding Array combination in Scala.
The following works fine:
scala> Array('0', '1', '2') ++ Array('A', 'B', 'C')
res0: Array[Char] = Array(0, 1, 2, A, B, C)
But this one does not:
scala> ('0' to '9').toArray ++ ('A' to 'Z').toArray
<console>:8: error: polymorphic expression cannot be instantiated to expected type;
found : [B >: Char]Array[B]
required: scala.collection.GenTraversableOnce[?]
('0' to '9').toArray ++ ('A' to 'Z').toArray
^
Moreover, the following seems to work:
scala> ('0' to '9').toArray
res1: Array[Char] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> ('A' to 'Z').toArray
res2: Array[Char] = Array(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z)
scala> res1 ++ res2
res3: Array[Char] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z)
Another interesting bit I found:
scala> (('0' to '9').toArray) ++ (('A' to 'Z').toArray[Char])
res4: Array[Char] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z)
I cannot really explain this - am I missing something?
The ++
Method is defined in ArrayOps
:
def ++[B](that: GenTraversableOnce[B]): Array[B]
Returns a new mutable indexed sequence containing the elements from the left hand operand followed by the elements from the right hand operand. The element type of the mutable indexed sequence is the most specific superclass encompassing the element types of the two operands.
The type of the Array in which the results are stored is inferred.
('0' to '9').toArray ++ ('A' to 'Z').toArray
('0' to '9').toArray
works great, and we get back an Array[Char]
.
8: materializing requested scala.reflect.type.ClassTag[Char] using `package`
.this.materializeClassTag[Char]...
According to the method definition we accept GenTraversableOnce[B]
. In our case, we accept GenTraversableOnce[Char]
.
If we re run scala with the -Ytyper-debug
option, we can see type inference in action.
//it tries to find the type of arg0
typing '0'.to('1').toArray.$plus$plus('A'.to('B').toArray): pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode, silent=false, context.owner=value res1
typing '0'.to('1').toArray.$plus$plus: pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode BYVALmode POLYmode FUNmode, silent=true, context.owner=value res1
typing '0'.to('1').toArray: pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode POLYmode QUALmode, silent=true, context.owner=value res1
typing '0'.to('1'): pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode POLYmode QUALmode, silent=true, context.owner=value res1
typing '0'.to: pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode BYVALmode POLYmode FUNmode, silent=true, context.owner=value res1
typing '0': pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode POLYmode QUALmode, silent=true, context.owner=value res1
typed '0': Char('0')
adapted '0': Char to ?,
//It succeeds.
//Now it infers a scala type
infer implicit view {
tree '0'
pt Char('0') => ?{def to: ?}
}
new ImplicitSearch(floatWrapper) {...}
...
new ImplicitSearch(charWrapper) {...}
}
//The interesting bit...
typedImplicit1 charWrapper, pt=Char('0') => ?{def to: ?}, from implicit charWrapper:(c: Char)scala.runtime.RichChar
Implicit search yielded: SearchResult(scala.this.Predef.charWrapper, )
typed scala.this.Predef.charWrapper('0').to('1'): scala.collection.immutable.NumericRange.Inclusive[Char]
adapted scala.this.Predef.charWrapper('0').to('1'): scala.collection.immutable.NumericRange.Inclusive[Char] to ?,
typed scala.this.Predef.charWrapper('0').to('1').toArray: [B >: Char](implicit evidence$1: scala.reflect.ClassTag[B])Array[B]
//repeat the process
Infer implicit {
tree scala.this.Predef.charWrapper('0').to('1').toArray[Char]
pt scala.reflect.ClassTag[Char]
undetparams
}
The inference process repeats, and the type is further inferred from the method call on that type, which is ++
. The type of ('0' to '1').toArray ++
is finally resolved to be ArrayOps
.
Implicit search yielded: SearchResult(scala.this.Predef.charArrayOps, )
adapted this.charArrayOps(charWrapper('0').to('1').toArray[Char](ClassTag.Char)).++: [B >: Char, That](that: scala.collection.GenTraversableOnce[B])(implicit bf: scala.collection.generic.CanBuildFrom[Array[Char],B,That])That to ?, undetparams=type B, type That
Scala then types our second arg...
typed scala.this.Predef.charWrapper('A').to('B'): scala.collection.immutable.NumericRange.Inclusive[Char]
adapted scala.this.Predef.charWrapper('A').to('B'): scala.collection.immutable.NumericRange.Inclusive[Char] to ?,
typed scala.this.Predef.charWrapper('A').to('B').toArray: [B >: Char](implicit evidence$1: scala.reflect.ClassTag[B])Array[B]
Finally, we see if our Array[B]
has the GenTraversableOnce
trait.
infer implicit view {
tree <empty>
pt Array[?B] => scala.collection.GenTraversableOnce[?]
undetparams
}
Of course, it does not and we get :
typing private[this] val res1: <error> = '0'.to('1').toArray.$plus$plus('A'.to('B').toArray): pt = ?: undetparams=, implicitsEnabled=true, enrichmentEnabled=true, mode=EXPRmode BYVALmode, silent=false, context.owner=object $iw
typed private[this] val res1: <error> = scala.this.Predef.charArrayOps(scala.this.Predef.charWrapper('0').to('1').toArray[Char](ClassTag.Char)).++[B, That]('A'.to('B').toArray): <notype>
See https://stackoverflow.com/a/6124177/2823715 for information about why this is the case.
The solution is to use ++
:
def ++:[B >: T, That](that: collection.Traversable[B])(implicit bf: CanBuildFrom[Array[T], B, That]): That
It differs from ++ in that the right operand determines the type of the resulting collection rather than the left one. Mnemonic: the COLon is on the side of the new COLlection type.
It also makes an ArrayOps
from Arrays
, so that's what you need.
This is confirmed by our typing debug output:
typing {
<synthetic> val x$1 = '0'.to('1').toArray;
'A'.to('B').toArray.$plus$plus$colon(x$1)
}:
...
typed scala.this.Predef.charWrapper('0').to('1').toArray: [B >: Char](implicit evidence$1: scala.reflect.ClassTag[B])Array[B]
...
typing scala.this.Predef.charArrayOps(scala.this.Predef.charWrapper('A').to('B').toArray(ClassTag.Char())).++:(scala.this.Predef.wrapCharArray(x$1), scala.this.Array.canBuildFrom(ClassTag.Char())): pt = Array[Char]: undetparams=, implicitsEnabled=false, enrichmentEnabled=false, mode=EXPRmode BYVALmode, silent=false, context.owner=value res2
...
<synthetic> val x$1: Array[Char] = new runtime.RichChar(scala.this.Predef.charWrapper('0')).to(scala.Char.box('1')).toArray(ClassTag.Char()).$asInstanceOf[Array[Char]]();
scala.this.Predef.charArrayOps(new runtime.RichChar(scala.this.Predef.charWrapper('A')).to(scala.Char.box('B')).toArray(ClassTag.Char()).$asInstanceOf[Array[Char]]()).++:(scala.this.Predef.wrapCharArray(x$1), scala.this.Array.canBuildFrom(ClassTag.Char())).$asInstanceOf[Array[Char]]()
Seems related to: https://issues.scala-lang.org/browse/SI-2394
Perhaps it is just a limitation/unfixable bug related to Array
s. You can add a type parameter:
('0' to '9').toArray ++ ('A' to 'Z').toArray[Char]
to get it to work, whereas you don't need the type parameter with:
('0' to '9').toList ++ ('A' to 'Z').toList
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With