Runtime Staging

Фреймворк одновременно выражает метапрограммирование времени компиляции и многоэтапное программирование. Можно думать о метапрограммировании во время компиляции как о двухэтапном процессе компиляции: на первом пишется код в сплайсах верхнего уровня, который будет использоваться для генерации кода (макросы), и на втором выполняются все необходимые вычисления во время компиляции. И объектная программа, которая будет запускаться как обычно. Что, если бы можно было бы синтезировать код во время выполнения и предложить программисту ещё один дополнительный этап? Затем может быть значение типа Expr[T] во время выполнения, которое по существу можно рассматривать как типизированное синтаксическое дерево, доступное для показа в виде строки (красивая печать), либо скомпилировать и запустить. Если количество цитат превышает количество вставок более чем на единицу (эффективная обработка во время выполнения значений типа Expr[Expr[T]], Expr[Expr[Expr[T]]], ...), то говорится о многоэтапном программировании.

Мотивация этой парадигмы состоит в том, чтобы позволить информации времени выполнения влиять на генерацию кода или направлять ее.

Интуиция: этап, на котором выполняется код, определяется разницей между количеством областей вставки и областей цитат, в которые он встроен.

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

API

Платформа позволяет выполнять код поэтапно, т.е. быть готовым к выполнению на более позднем этапе. Для запуска этого кода в классе есть еще один метод в Expr с именем run. Обратите внимание, что $ и run обе преобразуют Expr[T] в T, но только $ подпадает под действие PCP, тогда как run - это обычный метод. scala.quoted.staging.run предоставляет Quotes, который можно использовать для отображения выражения в его области. С другой стороны scala.quoted.staging.withQuotes предоставляет Quotes без оценки выражения.

package scala.quoted.staging

def run[T](expr: Quotes ?=> Expr[T])(using Compiler): T = ...

def withQuotes[T](thunk: Quotes ?=> T)(using Compiler): T = ...

Создание нового проекта Scala 3 с включенным промежуточным размещением

sbt new scala/scala3-staging.g8

Из scala/scala3-staging.g8.

Создаст проект с необходимыми зависимостями и некоторыми примерами.

Если предпочитаете создавать проект самостоятельно, обязательно определите следующую зависимость в определении сборки build.sbt.

libraryDependencies += "org.scala-lang" %% "scala3-staging" % scalaVersion.value

и если используете scalac/scala напрямую, используйте флаг -with-compiler для обоих:

scalac -with-compiler -d out Test.scala
scala -with-compiler -classpath out Test

Пример

Теперь возьмем точно такой же пример, как в макросах. Предположим, что мы не хотим передавать массив статически, а генерируем код во время выполнения и передаем значение также во время выполнения. Обратите внимание, как создается функция будущей стадии типа Expr[Array[Int] => Int] в строке 6. С помощью staging.run{ ... } можно оценить выражение во время выполнения. В рамках staging.run также можно вызвать show, чтобы получить исходное представление выражения.

import scala.quoted.*

// make available the necessary compiler for runtime code generation
given staging.Compiler = staging.Compiler.make(getClass.getClassLoader)

val f: Array[Int] => Int = staging.run {
  val stagedSum: Expr[Array[Int] => Int] =
    '{ (arr: Array[Int]) => ${sum('arr)}}
  println(stagedSum.show) // Prints "(arr: Array[Int]) => { var sum = 0; ... }"
  stagedSum
}

f.apply(Array(1, 2, 3)) // Returns 6

Ссылки: