добавить замыкания. Хочу уточнить правильно ли я понимаю, что мне вообще нужно делать. Раньше мне сказали, что в качестве gc на первое время будет достаточно обычного реф каунтера. Как я представляю реализацию:
1) В структуре представляющей замыкание есть поле содержащее набор идентификаторов захватываемых переменных, которое заполняется при компиляции(Vec<&str>) и свой стек который заполняется при интерпретации(Vec<(&str, Rc<Value>)>).
2) Когда встречается лямбда выражение, помимо его компиляции мне также нужно составить список идентификаторов, которые в нем встречаются, но не объявлены в аргументах/теле функции.
3) Уже во время выполнения байткода при объявлении функции я для каждого идентификатора из первого поля ищу его в стеке и добавляю во второе поле пару вместе со значением.
4) При вызове какого-либо замыкания я пары из второго поля добавляю в текущий стек
Ещё у меня возникла пара вопросов:
1) Как поступают с глобальными переменными? Стоит ли сохранять их значение во время объявления функции?
2) Во втором шаге, нужно ли пытаться совместить компиляцию и этот анализ в один проход, или анализ будет нормально сделать уже отдельно?
> содержащее набор идентификаторов захватываемых переменных Зачем захватывать идентификаторы переменных? Лучше сразу захватывать значения, которые лежат в этих переменных. > и свой стек который заполняется при интерпретации Зачем там свой стек? Это ж замыкания, а не продолжения? > При вызове какого-либо замыкания я пары из второго поля добавляю в текущий стек Зачем их на стек перекладывать? Проще взять прямо оттуда, где они хранятся. > Стоит ли сохранять их значение во время объявления функции? Да. Это называется "лексическая область видимости" (lexical scope). Вообще, имеет смысл посмотреть что такое lambda lifting и как это делается. Но по-простому можно прямо в байт-код добавить инструкции для создания замыканий и работы с ними.
> Зачем захватывать идентификаторы переменных? Лучше сразу захватывать значения, которые лежат в этих переменных. А как узнать сами значения во время компиляции?
Так само замыкание создаётся во время выполнения.
Тогда анализ тела функции тоже во время интерпретации делать?
Почему? Как тогда Вы сгенерируете код для создания замыкания, если Вы не проанализировали функцию, и не знаете, что нужно будет делать?
А если замкнутые переменные нужно менять?
> В структуре представляющей замыкание есть поле содержащее набор идентификаторов Так, стоп. Это поминки. Замыкание может захватить локальный объект, уйти за пределы области определения и там быть вызванным. В этом случае всё развалится. Надо захватывать стек (контекст) на момент создания замыкания.
Менять нужно значения, а не переменные — не надо это путать.
> Надо захватывать стек (контекст) на момент создания замыкания. Ну, весь стек захватывать не нужно — это же не продолжение...
Если мы не весь стек захватываем, то у нас видимость перестаёт быть лексической
Значения переменной…
Я предлагаю не делать мутабельных переменных вообще, а докинуть их потом, как разберёмся с иммутабельными
Но весь стек то зачем, только те переменные, которые используются непосредственно в теле функции, нет? А то получится иррационально
Это частный случай захвата всего. Обычно быстрее захватить стек, чем проходить по нему и выбирать конкретные вещи — даже когда набор известен статически.
Обычно советуют обратное
Можно и конкретику. Это потребует отдельного прохода с выводом явных списков захвата у лямбд.
Это фундаментальное решение в дизайне, чтобы влепить их потом может понадобиться все переделать
В окамле вон справились, в хаскелле тоже
Обратно-совместимо, без введения нового синтаксиса для этих переменных?
Нет. let a = ref 42
Решаемо юзерспейсом. Добавить конструктор и операторы на чтение/запись. let a = ref 4; a := 5; print (a!);
Давайте не будем подменять понятия - это не изменение переменой, а просто структура с изменяемым содержимым, плотно обмазанная сахаром
М-да уж, в интерпретатор Nutt понадобилось очень много изменений внести, чтобы добавить квалификатор ссылки mut.
Обсуждают сегодня