Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interesting findings when combining Arrays in Scala

Tags:

arrays

scala

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?

like image 348
argyakrivos Avatar asked Nov 20 '13 13:11

argyakrivos


2 Answers

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]]()
like image 96
rahilb Avatar answered Nov 11 '22 07:11

rahilb


Seems related to: https://issues.scala-lang.org/browse/SI-2394

Perhaps it is just a limitation/unfixable bug related to Arrays. 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
like image 23
Dave L. Avatar answered Nov 11 '22 08:11

Dave L.