#scala #jvm-bytecode
#scala #jvm-байт-код
Вопрос:
val f = { x :Int => x }
val g = f.asInstanceOf[Int, Unit]
g(1) //works
Насколько хрупким является приведенный выше код? В настоящее время это работает, но, конечно, это не является частью спецификации языка. По этой причине, конечно, возможно, что однажды этого не произойдет; мой вопрос в том, насколько это вероятно, понимается в смысле разумных альтернатив для компилятора. Я плохо знаю байт-код, но я мог бы легко представить, что он ломается, пытаясь поместить an Int
в Unit
регистр. С другой стороны, эта работа может фактически быть недокументированным следствием документированной спецификации
компилятора Scala и самого байт-кода Java.
Моя мотивация заключается в первую очередь в @specialized
коде. Например, Iterable
содержит следующий метод:
trait Iterable[E] /* ... */
def foreach[T](f :E => T) :Unit
Вероятно, эта подпись была выбрана для того, чтобы разрешить передачу любой функции E
, которая может быть в качестве аргумента.
Однако на практике это почти всегда будет значение лямбда / метода. Проблема с приведенным выше методом
заключается в том, что для этого потребуется специализироваться на обоих E
и T
, что приводит к квадратному числу вариантов метода, чего следует избегать. Не специализироваться на T
бессмысленно, потому что scalac не выделяет @specialized
подклассы для подмножеств параметров типа: если какой-либо из @specialized
параметров получит ссылочный (или неспециализированный) тип в качестве аргумента, весь объект будет экземпляром удаленного суперкласса. Аналогично, нет apply(Int):Object
метода — если возвращаемый тип стерт, то apply(Object):Object
будет вызван erased, что приведет к боксу. Таким образом, в этом случае было бы гораздо более желательно иметь
def foreach(f :E => Unit) :Unit
Введение такого метода (с другим именем) и использование его в качестве целевого объекта делегирования обычного foreach
возможно и не приводит к какому-либо боксу, по крайней мере, до тех пор, пока созданный лямбда-выражение не имеет ссылочного типа в качестве возвращаемого типа.
Комментарии:
1. Я немного растерялся, мотивом этого вопроса является то, что вы хотите добавить свою собственную итерацию , которая является специализированной?
2. Моя собственная коллекция, в этом примере, да. И иметь линейное число, а не n ^ 2, специализированных методов.
Ответ №1:
Зависит от контекста.
Большую часть времени Scala будет компилировать код, так что функция, возвращающая Unit
, будет вызываться как JVM void
(без возвращаемого результата) или будет рассматривать ее как единственное возвращаемое scala.Unit
значение, которое можно отбросить. Пока JVM не будет пытаться вызвать что-либо для этого возвращаемого значения, она будет только предполагать, что это то java.lang.Object
, что следует проверять scala.Unit
, если оно используется для чего-либо, чего никогда не может произойти.
Но это не значит, что это невозможно.
val f: Int => Int = _ * 2
val g: Unit => Int = _ => 10
(f.asInstanceOf[Int => Unit] andThen g)(10)
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class scala.runtime.BoxedUnit (java.lang.Integer is in module java.base of loader 'bootstrap'; scala.runtime.BoxedUnit is in unnamed module of loader 'app')
scala.Function1.$anonfun$andThen$1(Function1.scala:85)
scala.Function1.apply$mcII$sp(Function1.scala:69)
ammonite.$sess.cmd27$.<clinit>(cmd27.sc:1)
Пока вы вызываете ее в своем собственном коде, что у вас есть полный контроль, Всегда знаете контекст, тест и т. Д., Все должно быть в порядке.
Что, по моему мнению, является плохим предположением практически для каждой библиотеки в мире или любой кодовой базы, которая будет поддерживаться более одной недели.
f.andThen(_ => ())
всегда было бы безопаснее, и у меня должны были быть действительно веские причины использовать оптимизацию, например .asInstanceOf
, и рисковать некоторыми забавными ошибками в будущем.
Ответ №2:
asInstanceOf
является по своей сути небезопасным универсальным аварийным люком. Когда вы его используете, вы действительно говорите проверяющему типу слепо доверять вам.
В общем, когда вы пишете x.asInstanceOf[Y]
, вы даете обещание компилятору, что значение x
действительно имеет тип Y
.
Это гарантированно работает только в том случае, если x
действительно имеет тип Y
. Если нет, компилятор выполнит некоторую специальную обработку, если x
статически известно, что это примитивный тип, или null
. Тогда произойдет одно из двух:
- Среда выполнения раскусит вашу ложь и выдаст a
ClassCastException
. - Вам сходит с рук ваша ложь и, возможно, другие вещи нарушают линию.
В вашем примере f имеет тип Int => Int
, который явно не является подтипом Int => Unit
. Другими словами: вы лжете компилятору, и все гарантии недействительны.
Мораль этой истории такова: вы никогда не должны полагаться на детали реализации для корректности, и вы всегда должны относиться asInstanceOf
к ней как к небезопасной.
Как писал Матеуш, f.andThen(_ => ())
или, что эквивалентно { x => {f(x); ()}}
, это единственный правильный способ преобразовать некоторые A => B
в A => Unit
.
Вы также должны отметить, что, хотя возможно генерировать специализированные методы, которые принимают специализированные функции в качестве аргументов, (x => x).asInstanceOf[Int => Unit]
они всегда будут возвращать неспециализированный Function1[Int,Unit]
, а не специализированный тип, так что это вам совсем не поможет с производительностью.