Hacker News Digest

Тег: #racket

Постов: 2

Compiling a Lisp: Lambda lifting (bernsteinbear.com)

Переписал Ghuloum-туториал на Python (~300 строк). Убрал читалку S-выражений и бинарный код — теперь текстовая ассемблерная печать.

Lambda-lifting требует:

  • знать связанные переменные;
  • собирать свободные переменные лямбд;
  • накапливать создаваемые code-объекты.

Связывают let и lambda; для них обновляем окружение.

Lifter

class LambdaConverter:
    def __init__(self):
        self.labels = {}

    def convert(self, expr, bound, free):
        match expr:
            case int() | Char() | bool():
                return expr
            case str() if expr in bound or expr in BUILTINS:
                return expr
            case str():
                free.add(expr)
                return expr
            case ["if", t, c, a]:
                return ["if",
                        self.convert(t, bound, free),
                        self.convert(c, bound, free),
                        self.convert(a, bound, free)]

lift_lambdas запускает обход и возвращает (labels …).

Lambda

Лямбда:

  • связывает параметры;
  • выделяет код;
  • захватывает внешнее окружение.

Пример:

(lambda () x)  ; x свободна

превращается в

(labels ((f0 (code () (x) x)))
  (closure f0 x))

Даже если x связан снаружи, внутри лямбды он считается свободным.

by azhenley • 10 августа 2025 г. в 22:35 • 146 points

ОригиналHN

#python#lisp#compiler#lambda-lifting#racket#scheme#c#c++#cuda#artificial-intelligence

Комментарии (15)

  • Участники рекомендуют три современные книги по компиляторам, вдохновлённые статьёй Ghuloum: «Writing a C Compiler» (Sandler), «Essentials of Compilation» на Racket и Python (Siek).
  • Обсуждали «lambda lifting»: преобразование, выносящее замыкания наверх, уменьшая их размер вплоть до полного исчезновения.
  • Уточнили, что «lambda lifting» в статье связан с разделом 3.11 о сложных константах в Scheme.
  • Разбирали, почему современный ИИ использует Python, а не Lisp: удобство как «клея» для C++/CUDA, упадок доли рынка Lisp и смена парадигмы ИИ.

Why tail-recursive functions are loops (kmicinski.com)

Хвостовая рекурсия превращает рекурсию в цикл: компилятор заменяет вызов на безусловный jmp, поэтому стек не растёт.

Обычная рекурсия кладёт промежуточные значения в стек, тратит O(n) памяти и вытесняет кэш.
Цикл же держит результат в аккумуляторе, использует O(1) памяти и линейное время.

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

Пример суммы списка

Обычная версия:

(define (sum l)
  (if (empty? l) 0
      (+ (first l) (sum (rest l)))))

Хвостовая версия:

(define (sum l acc)
  (if (empty? l) acc
      (sum (rest l) (+ acc (first l)))))

Аргументы l и acc перезаписываются «на месте», как переменные цикла.

Упражнение 1 — счётчик чётных/нечётных:

(define (even-odd l [e 0] [o 0])
  (if (empty? l) (cons e o)
      (let ([x (first l)])
        (if (even? x)
            (even-odd (rest l) (add1 e) o)
            (even-odd (rest l) e (add1 o))))))

Упражнение 2 — сглаживание дерева:
используйте аккумулятор-список и обход в обратном порядке, чтобы сохранить хвостовой вызов.

by speckx • 08 августа 2025 г. в 15:10 • 115 points

ОригиналHN

#recursion#tail-call-optimization#racket#functional-programming#algorithms

Комментарии (121)

  • Теоретически хвостовая рекурсия и циклы эквивалентны: любую хвостовую рекурсию можно превратить в цикл (и наоборот), но взаимно-рекурсивные функции требуют дополнительной работы.
  • На практике циклы чаще проще для чтения и не ломают стек, тогда как хвостовая рекурсия нуждается в оптимизации (TCO), которую не все языки поддерживают (Python, V8 её нет).
  • Некоторые языки (Scala, Clojure, F#) дают компромиссные конструкции (@tailrec, recur), сохраняющие функциональный стиль и гарантирующие отсутствие переполнения стека.
  • Вместо явной хвостовой рекурсии часто достаточно высокоуровневых комбинаторов вроде fold/map, но они не всегда позволяют досрочный выход и могут расходовать O(N) памяти.
  • Участники сходятся во мнении: владеть обоими подходами полезно, выбор зависит от языка, задачи и привычек команды.