Интересные результаты при объединении массивов в Scala

У меня возникли проблемы с пониманием комбинации массивов в Scala.

Следующее прекрасно работает:

scala> Array('0', '1', '2') ++ Array('A', 'B', 'C')
res0: Array[Char] = Array(0, 1, 2, A, B, C)

Но этого нет:

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
                                                   ^

Кроме того, работает следующее:

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)

Еще один интересный бит, который я нашел:

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)

Я не могу это объяснить, я чего-то не хватает?

Ответ 1

Метод ++ определяется в ArrayOps:

def ++[B](that: GenTraversableOnce[B]): Array[B]

Возвращает новую изменяемую индексированную последовательность, содержащую элементы из левый операнд, за которым следуют элементы правой руки операнд. Тип элемента изменяемой проиндексированной последовательности - это наиболее специфический суперкласс, охватывающий типы элементов двух операнды.

Выводится тип массива, в котором хранятся результаты.

('0' to '9').toArray ++ ('A' to 'Z').toArray

('0' to '9').toArray отлично работает, и мы возвращаем Array[Char].

8: materializing requested scala.reflect.type.ClassTag[Char] using `package`
.this.materializeClassTag[Char]...

В соответствии с определением метода мы принимаем GenTraversableOnce[B]. В нашем случае мы принимаем GenTraversableOnce[Char].

Если мы запустим scala с опцией -Ytyper-debug, мы можем увидеть вывод типа в действии.

//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
 }     

Процесс вывода повторяется, и тип далее выводится из вызова метода этого типа, который равен ++. Тип ('0' to '1').toArray ++ окончательно разрешен как 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 затем набирает наш второй аргумент...

 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]

Наконец, мы видим, что наш Array[B] имеет черту GenTraversableOnce.

 infer implicit view {
 tree         <empty>
  pt           Array[?B] => scala.collection.GenTraversableOnce[?]
  undetparams
}

Конечно, это не так, и мы получаем:

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>

См. fooobar.com/questions/198755/... для получения информации о том, почему это так.

Решение состоит в использовании ++:

def ++:[B >: T, That](that: collection.Traversable[B])(implicit bf: CanBuildFrom[Array[T], B, That]): That

Он отличается от ++ тем, что правый операнд определяет тип результирующая коллекция, а не левая. Мнемоника: КОЛОН находится на стороне нового типа COLUM.

Он также делает ArrayOps от Arrays, так что вам нужно.

Это подтверждается нашим вводом вывода отладки:

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]]()

Ответ 2

Кажется, связано с: https://issues.scala-lang.org/browse/SI-2394

Возможно, это всего лишь ошибка ограничения/ошибки, связанная с Array s. Вы можете добавить параметр типа:

('0' to '9').toArray ++ ('A' to 'Z').toArray[Char]

чтобы заставить его работать, в то время как для параметра типа не требуется:

('0' to '9').toList ++ ('A' to 'Z').toList