Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scala Macros и алгоритм Foetus

Scala Macros и алгоритм Foetus

Доклад, сделанный на встрече SCALA MOSCOW 25.05.2014. http://www.meetup.com/Scala-Moscow/events/171706192/
Video: http://vimeo.com/93133945

Ilya Klyuchnikov

April 25, 2014
Tweet

More Decks by Ilya Klyuchnikov

Other Decks in Programming

Transcript

  1. 1 Scala Macros и алгоритм Foetus Илья Ключников (JetBrains, Институт

    Прикладной Математики им. Келдыша) @lambdamix SCALA MOSCOW 25.04.2014
  2. 2 Сюжет • https://github.com/ilya-klyuchnikov/foetus - termination checker для подмножества Scala

    в 300 строк кода • Иллюстрация алгоритма Foetus • Иллюстрация, как работают макросы
  3. 3 Мотивация • Легковесное вложение зависимых типов (теории типов) в

    код на Scala • Легковесные специализированные расширения (контракты, эффекты, и тд) • EDSLs
  4. 5 Задача Проверка функций на тотальность (termination checking). There is

    no free lunch. - Выразительность зависимых типов основана (среди прочего) на тотальности.
  5. 6 Подмножество Scala • Данные - ADT. Запечатанная иерархия case-class'ов.

    • Внутри методов только простые case-выражения, вызовы методов и конструкторы. • Язык первого порядка.
  6. 7 AST case class Def(name: String, params: List[String], body: Term)

    sealed trait Term case class Var(n: String) extends Term case class App(h: String, args: List[Term]) extends Term case class Ctr(name: String, args: List[Term]) extends Term case class Case(selector: Term, branches: List[Branch]) extends Term case class Branch(pat: Pat, body: Term) case class Pat(name: String, params: List[String])
  7. 8 Макрос как парсер it("add") { parseDefs { def add(x:

    Nat, y: Nat): Nat = x match { case Z() => y case S(x1) => S(add(x1, y)) } } should equal { List( Def("add", List("x", "y"), Case(Var("x") , List( Branch(Pat("Z", List()), Var("y")), Branch(Pat("S", List("x1")), Ctr("S", List(App("add", List(Var("x1"), Var("y")))))) ))))}}
  8. 9 Макрос как парсер import scala.language.experimental.macros import scala.reflect.macros._ object parser

    { def parseDefs(expr: Any): List[Def] = macro parseDefsImpl def parseDefsImpl(c: blackbox.Context) (expr: c.Expr[Any]): c.Expr[List[Def]] = { … } }
  9. 11 Алгоритм Foetus • Funktionale – Obgleich Eingeschränkt – Termination

    Untersuchende Sprache • MuTTI (Munich Type Theory Implementation) • Agda Termination checker основан на Foetus • Andreas Abel. foetus – termination checker for simple functional programs. Programming Lab Report (1998) • Andreas Abel and Thorsten Altenkirch. A predicative analysis of structural recursion. JFP 12.1 (2002)
  10. 12 Основные идеи • Достаточное условие завершаемости. • Структурная рекурсия.

    При рекурсивном вызове аргументы должны уменьшаться. • Анализ “уменьшаемости” просходит внутри одного метода (интрапроцедурный анализ) • Затем результаты комбинируются с учетом зависимостей между методами (граф вызовов)
  11. 13 Матрица вызова def add(x: Nat, y: Nat): Nat =

    x match { case Z() => y case S(x1) => S(add(x1, y)) } def mult(x: Nat, y: Nat): Nat = x match { case Z() => Z() case S(x1) => add(y, mult(x1, y)) }
  12. 14 Матрица вызова def add(x: Nat, y: Nat): Nat =

    x match { case Z() => y case S(x1) => S(add(x1, y)) } def mult(x: Nat, y: Nat): Nat = x match { case Z() => Z() case S(x1) => add(y, mult(x1, y)) } add add mult add mult 1 2 3
  13. 15 Матрица вызова def add(x: Nat, y: Nat): Nat =

    x match { case Z() => y case S(x1) => S(add(x1, y)) } def mult(x: Nat, y: Nat): Nat = x match { case Z() => Z() case S(x1) => add(y, mult(x1, y)) } add add mult add mult 1 2 3 < ? ? = 1 ? = ? ? 2 < ? ? = 3
  14. 16 Достаточное условие для f→f def add(x: Nat, y: Nat):

    Nat = x match { case Z() => y case S(x1) => S(add(x1, y)) } def mult(x: Nat, y: Nat): Nat = x match { case Z() => Z() case S(x1) => add(y, mult(x1, y)) } add add mult add mult 1 2 3 < ? ? = 1 ? = ? ? 2 < ? ? = 3 На диагонали есть <
  15. 17 Матрица вызова → граф вызовов def add(x: Nat, y:

    Nat): Nat = x match { case Z() => y case S(x1) => S(add(x1, y)) } def mult(x: Nat, y: Nat): Nat = x match { case Z() => Z() case S(x1) => add(y, mult(x1, y)) } add add mult add mult 1 2 3 < ? ? = 1 ? = ? ? 2 < ? ? = 3 add mult
  16. 18 Несколько рекурсивных вызовов def ack(x: Nat, y: Nat): Nat

    = x match { case Z() => S(y) case S(x1) => y match { case Z() => ack(x1, S(Z())) case S(y1) => ack(x1, ack(S(x1), y1)) } }
  17. 19 Несколько рекурсивных вызовов def ack(x: Nat, y: Nat): Nat

    = x match { case Z() => S(y) case S(x1) => y match { case Z() => ack(x1, S(Z())) case S(y1) => ack(x1, ack(S(x1), y1)) } } 1 2 3
  18. 20 Несколько рекурсивных вызовов def ack(x: Nat, y: Nat): Nat

    = x match { case Z() => S(y) case S(x1) => y match { case Z() => ack(x1, S(Z())) case S(y1) => ack(x1, ack(S(x1), y1)) } } 1) < ? 2) < ? 3) = < 1 2 3
  19. 21 Несколько рекурсивных вызовов def ack(x: Nat, y: Nat): Nat

    = x match { case Z() => S(y) case S(x1) => y match { case Z() => ack(x1, S(Z())) case S(y1) => ack(x1, ack(S(x1), y1)) } } 1) < ? 2) < ? 3) = < 1 2 3 Лексикографический порядок
  20. 22 Взаимная рекурсия def ack(x: Nat, y: Nat): Nat =

    x match { case Z() => S(y) case S(x1) => ack(x1, ack1(y, S(x1))) } def ack1(y: Nat, x: Nat): Nat = y match { case Z() => S(Z()) case S(y1) => ack(x, y1) } ack ack1 m1 m3 m2
  21. 23 Взаимная рекурсия • ack → ack1 → ack -

    ??? • Перемножаем матрицы!! (Док-во корректности в статьях) • Матрицы перемножаются обычным образом, нужно лишь определить * и + для =, <, ?. ack ack1 m1 m3 m2
  22. 24 *, + + < = ? < < <

    < = < = = ? < = ? * < = ? < < < ? = < = ? ? ? ? ?
  23. 26 Алгоритм Foetus • Для каждого вызова f → g

    строится матрица вызова – как соотносятся параметры/аргументы через <,=,?. • Матрицами помечаем дуги в графе вызовов. • Насыщаем граф вызовов. • Для каждого набора дуг/матриц f → f проверяем наличие лексикографического порядка.
  24. 27 Алгоритм Foetus • Для каждого вызова f → g

    строится матрица вызова – как соотносятся параметры/аргументы через <,=,?. • Матрицами помечаем дуги в графе вызовов. • Насыщаем граф вызовов. • Для каждого набора дуг/матриц f → f проверяем наличие лексикографического порядка.
  25. 29 Compile-time metaprogramming • Макрос работает во время компиляции •

    Переписывание кода на Scala кодом на Scala
  26. 30 Как устроен parseDefs it("add") { parseDefs { def add(x:

    Nat, y: Nat): Nat = x match { case Z() => y case S(x1) => S(add(x1, y)) } } should equal { List( Def("add", List("x", "y"), Case(Var("x") , List( Branch(Pat("Z", List()), Var("y")), Branch(Pat("S", List("x1")), Ctr("S", List(App("add", List(Var("x1"), Var("y")))))) ))))}}
  27. 31 Как устроен parseDefs object parser { def parseDefs(expr: Any):

    List[Def] = macro parseDefsImpl def parseDefsImpl(c: blackbox.Context) (expr: c.Expr[Any]): c.Expr[List[Def]] = { import c.universe._ … def parseBlock(tree: Tree): c.Expr[List[Def]] = tree match { case Block(stmts, _) => val defs = parseStmts(stmts) reify(qList(defs.map(qDef(_))).splice) } parseBlock(expr.tree) } }
  28. 34 Reify/Splice def qTerm(t: Term): Expr[Term] = t match {

    case Var(n) => reify { Var(qs(n).splice) } case Ctr(n, args) => reify { Ctr(qs(n).splice, qList(args.map(qTerm(_))).splice ) } case App(n, args) => reify { App(qs(n).splice, qList(args.map(qTerm(_))).splice ) } case Case(s, bs) => reify { Case(qTerm(s).splice, qList(bs.map(qBranch(_))).splice) }}
  29. 35 Как работает it("direct ack") { orderDefs(parseDefs { def ack(x:

    Nat, y: Nat): Nat = x match { case Z() => S(y) case S(x1) => y match { case Z() => ack(x1, S(Z())) case S(y1) => ack(x1, ack(S(x1), y1)) } } }) should equal { List("ack" -> Some(List(0, 1))) } }