Создание Fizzbuzz во Fractran снизу вверх

Созданиеfizzbuzzвоfractranснизувверх

В этом посте я покажу вам, как писать Fizzbuzz в язык программирования Fractran. Если вы не знаете, Fractran – это эзотерический язык программирования. Это означает, что написать любую программу на Fractran чрезвычайно сложно. Чтобы смягчить это затруднение, вместо того, чтобы писать Fizzbuzz в сыром Fractran, мы собираемся создать язык, который компилируется в Fractran, а затем написать Fizzbuzz на этом языке.

Этот пост разбит на три части. Первая часть описывает, что такое Fractran, и способ понять, что делает программа Fractran. Во второй части будут рассмотрены основы языка, который мы создадим, и то, как он будет отображаться во Fractran. Наконец, в Части 3 мы продолжим добавлять новые функции в язык, пока не станет проще писать на нем Fizzbuzz.


Часть 1: Понимание Fractran

Прежде, чем мы сможем Чтобы начать писать программы на Fractran, мы должны сначала понять, что такое Fractran. Программа Fractran представлена ​​просто списком дробей. Чтобы выполнить программу Fractran, вы начинаете с переменной N = 2. Затем вы просматриваете список дробей, пока не найдете дробь F, такую, что N F является целым числом. Затем вы устанавливаете N = N F и возвращаетесь к началу списка дробей. Вы продолжаете повторять этот процесс до тех пор, пока не будет дробной части F такой, что N F является целым числом.

Поскольку нет возможности распечатать что-либо с помощью обычных правил Fractran , мы собираемся добавить одно дополнительное правило поверх обычных. В дополнение к списку дробей каждая программа будет иметь отображение чисел в символы, представляющие алфавит программы. После умножения N на F, всякий раз, когда новое N кратно одному из чисел в алфавите, будет напечатан символ, которому соответствует это число. Я написал функцию run-фрактран , которая реализует эту версию Fractran и включила ее здесь. Он принимает список дробей и алфавит как список и выполняет программу.

Давайте рассмотрим простой пример. Допустим, у нас есть следующая программа Fractran:

 9/2, 1/5, 5/3 

с алфавитом 5-> a. Чтобы запустить эту программу, мы начинаем с N = 2. Затем мы просматриваем список дробей, пока не найдем дробь F такую, что N F является целым числом. На этом первом шаге F становится 9/2, поскольку N F = 2 9/2 = 9, что является целым числом. Затем мы устанавливаем N равным N F, так что N теперь становится 9. Повторяя этот процесс снова, мы получаем F = 5/3 и N = N F = 15. Поскольку число 5 находится в алфавите, а N теперь кратно 5, мы выводим символ, которому соответствует 5, a. Если мы продолжаем повторять эти шаги, мы в конечном итоге достигнем точки, где N = 1, и мы выведем строку aa. Поскольку 1 раз любая из дробей не приводит к целому числу, программа завершается выводом aa.

На этом этапе вы можете подумать, что написание любого программа во Fractran практически невозможна. На самом деле есть простой прием, который значительно упрощает программу Fractran. Все, что вам нужно сделать, это посмотреть на факторизацию всех чисел на простые множители. Давайте посмотрим, как выглядит приведенная выше программа Fractran, если мы преобразуем каждое число в кортеж (a, b, c), где a – это сколько раз 2 делит число, b – сколько раз 3, а c – сколько раз 5 делает. Затем программа принимает следующий вид:

 (0, 2, 0) / (1, 0, 0) ( 0, 0, 0) / (0, 0, 1) (0, 0, 1) / (0, 1, 0) 

У нас также есть отображение кортежа (0,0,1) в a для нашего алфавита. Начнем с N = (1,0,0). Если вы не знаете, умножение двух чисел – это то же самое, что сложение количества каждого простого множителя, а деление – то же самое, что вычитание количества. Например, 2 6 = (1,0,0) + (1,1,0) = (2,1,0) = 12. При таком взгляде на программу поиск дроби F такой, что N F является целым числом, превращается в поиск дроби F такой, что каждый элемент в кортеже N больше или равен соответствующему элементу в кортеже в знаменателе F. Как только мы находим такое F, вместо того, чтобы умножать N на него, вы вычитаете из каждого элемента N соответствующее значение в знаменателе F (эквивалентно делению на знаменатель) и добавляете соответствующее значение в числитель (эквивалентно умножение на числитель). Выполнение программы с такой интерпретацией происходит следующим образом.

Начнем с N = (1,0,0). Поскольку каждое значение в N больше или равно их соответствующим значениям в знаменателе первой дроби, мы вычитаем каждое значение в первом знаменателе, а затем складываем каждое значение в числителе, чтобы получить N = (1,0,0) ( 1,0,0) + (0,2,0) = (0,2,0). Повторяя это снова, F становится третьей дробью. Вычитая знаменатель и прибавляя числитель, мы получаем N = (0,1,1). Затем, поскольку каждое значение в N больше или равно соответствующему элементу в (0,0,1), мы печатаем a. Программа продолжается, как и в исходной программе Fractran.

В принципе, мы можем рассматривать каждое простое число как имеющее регистр, который может принимать неотрицательные значения. целочисленные значения. Каждая дробь – это инструкция, которая работает с некоторыми регистрами. Вы можете интерпретировать дробь как говорящую, что если текущее значение каждого регистра больше или равно значению, указанному в знаменателе (количество раз, когда простое число этого регистра делит знаменатель), вы вычитаете из регистров все значения в знаменателе, сложите все значения, указанные в числителе (количество раз, когда штрих каждого регистра делит числитель), а затем вернитесь к первой инструкции. В противном случае, если какой-либо регистр меньше значения, указанного в знаменателе, перейдите к следующей дроби. Например, дробь 9/2 может быть переведена в следующий псевдокод:

 ;;  Если регистр, соответствующий простому числу 2 ;;  больше или равно 1, если reg  > = 1 ;;  Уменьшить его на 1 и увеличить регистр ;;  соответствует 3 на 2. reg = reg  - 1 reg [3] = reg [3] + 2 перейти в начало программы ;;  В противном случае продолжите выполнение остальной части программы.  

Хотя программирование Fractran по-прежнему сложно, этот метод внезапно делает написание Fizzbuzz на Fractran легко управляемым.


Часть 2: Компиляция во Fractran

Для нашего компилятора нам нужно будет сгенерировать много простых чисел. Для этого мы будем использовать функцию new-prime , которая сгенерирует другой простое число при каждом вызове. »

 (defun prime (n)« Является ли N простым числом? «(цикл для i от 2 до (isqrt n) never (multiple ni))) (defparameter next-new-prime nil) (defun new-prime ()« Возвращает новое простое число, которое мы еще не использовали ». (prog1 next-new-prime (setf next-new-prime (цикл для i из (+ next-new-prime 1) if (prime i) return i)))) 

Итак, теперь, когда у нас есть new-prime , мы можем начать выяснять, как мы собираемся скомпилировать Fractran. Первая деталь, которую нам нужно выяснить, – это как выразить поток управления во Fractran. Другими словами, нам нужен способ указать, какие дроби будут выполняться после дробей друг за другом. Это проблема, потому что после выполнения дроби вы всегда возвращаетесь к первой дроби.

Выражение потока управления на самом деле оказывается на удивление простым. Для каждой дроби мы можем обозначить регистр. Затем мы выполняем только дробь, если ее регистр установлен. Легко выполнить условное выполнение дроби в зависимости от того, установлен ли ее регистр, с помощью трюка, который мы используем для интерпретации программы Fractran. Все, что нам нужно сделать, это умножить знаменатель каждой дроби на простое число регистра этой дроби. Таким образом, мы пропустим дробь, если ее регистр не установлен. Кроме того, все, что нам нужно сделать, чтобы указать, какая дробь должна выполняться после данной дроби, – это умножить числитель данной дроби на штрих регистра следующей дроби. Таким образом, после выполнения дроби, он установит регистр следующей дроби.

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

 (defparameter cur-inst-prime nil) (defparameter next-inst-prime nil) 

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

 (defun advance () (setf cur-inst-prime next-inst-prime next-inst-prime (new-prime))) 

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

Простейший вид выражения, который нам понадобится, – это встроенная дробь. Если выражение Лисптрана – это всего лишь дробь, мы можем просто добавить эту дробь к генерируемой программе Fractran.

Еще один полезный вид выражения – метки. Всякий раз, когда выражение Лисптрана является символом Лиспа, мы можем интерпретировать это как метку. Каждая метка будет преобразована в ту дробь, которая является штрихом следующей инструкции после метки, деленной на штрих метки. Таким образом, мы можем перейти к инструкции после метки, установив регистр для метки. Чтобы упростить отслеживание простых чисел меток, мы собираемся вести хеш-таблицу lisptran-labels , отображение меток на простые числа этих меток. У нас также будет функция простого числа для метки , которая будет искать простое число для обозначить или назначить новое простое число, если оно еще не назначено:

 (defparameter lisptran-labels nil) (defun prime-for-label (label) (or (gethash label lisptran-labels  (setf (gethash label lisptran-labels  (new-prime)))) 

Последний вид выражения, который будет полезен, – это вызовы макросов. Вызов макроса будет списком, первым элементом которого является имя макроса, за которым следует список произвольных выражений Лиспа (выражения не обязательно должны быть выражениями Fractran. Их можно интерпретировать так, как этого хочет макрос). Чтобы скомпилировать вызов макроса, мы найдем функцию, связанную с макросом, и вызовем ее по выражениям в остальной части вызова макроса. Затем эта функция должна вернуть список выражений Лисптрана, которые затем будут скомпилированы вместо вызова макроса. После этого мы просто продолжаем компилировать новый код, сгенерированный расширением макроса.

Чтобы отслеживать определения макросов, мы будем вести хеш-таблицу lisptran-macros , который будет отображать от имени макроса до функции для этого макроса. Чтобы упростить определение макросов Lisptran, мы можем создать макрос Lisp deftran , который работает аналогично defmacro . При определении макроса с помощью deftran вы на самом деле просто определяете функцию, которая будет принимать выражения в вызове макроса и вернуть список инструкций Lisptran, которые будут скомпилированы вместо него. Вот определение для deftran :

 (defparameter lisptran-macros (make-hash-table)) (defmacro deftran (name args & body body) «Определить макрос Lisptran.»  (setf (gethash ', name lisptran-macros  (lambda, args, @ body))) 

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

Хотя теперь у нас есть все необходимые выражения, есть еще несколько частей компилятора, которые нам нужно выяснить. Например, мы еще не придумали, как будем представлять переменные. В конечном итоге это тривиально. Мы можем просто назначить регистр каждой переменной и сохранить отображение имен переменных на простые числа. таким же образом у нас есть отображение для меток:

 (defparameter lisptran-vars nil) ( defun prime-for-var (var) (или (gethash var lisptran-vars  (setf (gethash var lisptran-vars  (new-prime)))) 

Последняя часть компилятора, которую нам нужно выяснить, - это то, как мы собираемся представлять алфавит программы. Один из способов сделать это - просто представить символы нашего алфавита как переменные. Алфавит программы может быть просто всеми переменными, которые имеют символы для имен и простые числа регистров для этих переменных. Поступая таким образом, мы можем напечатать символ, просто увеличив, а затем немедленно уменьшив значение переменной! Вот код, который можно использовать для получения алфавита из lisptran-vars :

 (defun алфавит (vars) "Учитывая хэш -таблица переменных Лисптрана к простым числам, возвращает список, представляющий алфавит. "(цикл для var, являющегося хеш-ключами в vars, с использованием (хеш-значение простое) if (characterp var) collect (cons var prime))) 

Теперь, когда мы можем выразить поток управления, переменные и макросы, у нас есть все необходимое для записи фактического Лисптрана во Fractran. compiler:

 (defun Assembly (insts) «Скомпилировать данную программу Lisptran во Fractran. Возвращает два значения. Первая - это программа Fractran, а вторая - алфавит программы. "(Let ( cur-prime 2)  cur-inst-prime (new-prime))  next-inst-prime (new-prime))  lisptran-labels (make-hash-table))  lisptran-vars (make-hash-table))) (значения (ассемблерные инструменты) (алфавит lisptran-vars ))) (defun Assembl  e-helper (exprs) (if (null insts) '() (let ((expr (car exprs)) (rest (cdr exprs))) (cond ;;  Если это число, мы просто добавляем его к ;;  Программа Fractran, а все остальное скомпилируйте ;;  программы Lisptran ((numberp expr) (cons expr (ассемблер-помощник rest))) ;;  Если это символ, мы делим простое число на ;;  следующая инструкция по штриху для ;;  метка.  ((symbolp expr) (cons (/ cur-inst-prime (prime-for-label expr)) (ассемблерный вспомогательный остаток))) ;;  В противном случае это вызов макроса.  Мы ищем ;;  макрос, названный первым символом в ;;  выражение и вызовите его в остальной части ;;  остальные выражения в вызове макроса.  ;;  Затем мы добавляем все инструкции ;;  он возвращается остальной части программы ;;  и скомпилируйте это.  (: else (let ((macrofn (gethash (car inst) lisptran-macros )) (assembly-helper (append (apply macrofn (cdr inst)) rest))))))) 

Функция собрать принимает программу на Лисптране и возвращает два значения. Он возвращает сгенерированную программу Fractran и алфавит этой программы. assembly сначала инициализирует все глобальные переменные для программы, а затем переходит к ассемблер-помощник , который рекурсивно обрабатывает программу Lisptran в соответствии со спецификацией выше. Используя функцию run-фрактран , о которой я упоминал выше, мы можем написать функцию, которая будет выполнять заданную программу Lisptran следующим образом:

 (defun run-lisptran (insts) "Запустить данную программу Lisptran . "(вызов с несколькими значениями # 'run-фрактран (сборка экземпляров))) 

Часть 3: Создание Лисптрана

Теперь, когда мы завершили основной компилятор, мы можем начать добавлять в него фактические функции. С этого момента мы не будем касаться основного компилятора. Все, что мы собираемся сделать, это определить пару макросов Lisptran. В конце концов у нас будет достаточно макросов, так что программирование Lisptran будет похоже на программирование языка ассемблера высокого уровня.

Первые операции, которые мы собираемся определить, - это базовые арифметические операции. Например, сложение. Чтобы добавить дополнение к Lisptran, мы можем определить макрос addi , который означает добавить немедленный. Сразу означает, что мы знаем, какое число добавляем во время компиляции. Макрос a ddi примет переменную и число и расширится до дроби, которая добавит данное число в регистр переменной. В этом случае знаменатель дроби будет простым числом для текущей инструкции (выполнить эту инструкцию, когда этот регистр установлен), а числитель будет простым числом для следующей инструкции (выполнить следующую инструкцию после этой), умноженной на prime для переменной, возведенной в степень числа, которое мы добавляем (добавить непосредственное значение в регистр). Вот как выглядит определение для addi :

(deftran addi (xy) (prog1 (list (/ next (expt (prime-for-var x) y)) cur ) (advance)))

With также потребуется операция, которая выполняет вычитание. Это немного сложно, но мы можем реализовать макрос subi (немедленное вычитание) в терминах addi , поскольку добавление числа равносильно добавлению отрицательного значения этого числа:

 (deftran subi (xy)  ((addi x, (- y)))) 

Теперь, когда мы ' Если у вас есть несколько макросов для выполнения базовой арифметики, мы можем сосредоточиться на макросах, которые позволяют нам выразить поток управления. Первый макрос потока управления, который мы реализуем, это > = i (переход, если больше или равно к немедленному). Чтобы реализовать > = i , нам нужно разложить его на три дроби. Первая дробь проверяет, больше ли переменная непосредственному или равна ему. Если тест завершится успешно, мы перейдем ко второй дроби, которая восстановит переменную (поскольку, когда тест завершится успешно, все значения из знаменателя будут уменьшены из соответствующих регистров), а затем перейдем к метке, переданной в > = я . Если тест не удастся, мы перейдем к третьей дроби, которая просто перейдет к следующей дроби после этого.

Знаменателем первой дроби будет простое число для текущей инструкции (выполнить инструкцию, если этот регистр установлен), умноженное на простое число регистра, возведенное в степень константы (как мы проверяем, что регистр больше или равен непосредственному), а числитель будет простым для второй инструкции (так что переходим ко второй инструкции, если проверка прошла успешно). Вторая дробь - это просто штрих для метки, переданной в > = i (поэтому мы переходим туда, куда указывает этикетка) разделил штрих для этой инструкции. Наконец, знаменатель третьей дроби - это простое число для текущей инструкции (поэтому мы переходим к нему, если тест в первой дроби терпит неудачу), а числитель - это просто простое число для следующей инструкции, так что мы продолжаем это, если тест не проходит:

 (deftran> = i (var val label) (prog1 (let ((restore (new-prime))) (list (/ restore (expt (prime-for-var var) val) cur-inst-prime  (/  (метка простого-for-label) (expt (prime-for -var var) val)) restore) (/ next-inst-prime cur-inst-prime )) (advance))) 

Хотите верьте, хотите нет, но после этого нам больше не придется думать о дробях. Теперь у Lisptran достаточно основы, чтобы все дальнейшие макросы, которые нам понадобились, можно было выразить в терминах addi , subi и > = я . Единственные две функции, которые действительно необходимо реализовать в терминах Fractran, - это addi и > = я . Это означает, что больше не нужно думать о Fractran. С этого момента все, что у нас есть, - это Лисптран!

Мы можем легко определить безусловный переход в терминах > = я . Поскольку все регистры начинаются с 0, мы можем реализовать goto как больше или равно нуль. Мы используем функцию Lisp gensym для генерации переменной без имени, чтобы переменная не t конфликтует с любыми другими переменными Лисптрана:

 (deftran goto (label)  ((> = i, (gensym) 0, label))) 

Затем через комбинацию > = i и goto , мы можем определить < = i :

 (дефтран <= i (var val label) (let ((gskip (gensym)))  ((> = i, var ( +, val 1), gskip) (goto, label), gskip))) 

Теперь, когда у нас есть несколько макросы для выполнения потока управления, мы можем начать создавать некоторые утилиты для печати. Как упоминалось ранее, печать символа - это то же самое, что приращение переменной с символом в качестве имени и последующее его немедленное уменьшение:

 (deftran print-char (char)  ((addi, char 1) (subi, char 1))) 

Затем, если мы хотим написать макрос, который печатает строку, он может просто развернуться в серию вызовов для print-char , каждый из которых печатает один символ в строке:

 (deftran print-string (str) (цикл для char по str collect  (print-char, char))) 

Нам также понадобится функция для печати числа. Написать это с текущим состоянием Lisptran довольно сложно, поскольку мы еще не реализовали несколько утилит, таких как mod, но мы можем начать с реализации макроса print-digit , который выводит значение переменной от 0 до 9. Мы можем реализовать это, расширив его до ряда условий. Первый проверит, меньше ли переменная или равна нулю. Если это так, он напечатает нулевой символ и пропустит остальные условия. В противном случае он переходит к следующему условию, которое проверяет, меньше или равна ли переменная единице, и так далее. Нам не нужно вручную писать код для печатной цифры , потому что мы можем использовать Lisp чтобы сгенерировать для нас код:

 (deftran print-digit (var) (цикл с gend = ( gensym) для i от 0 до 9 для gprint = (gensym) для gskip = (gensym) append  ((<= i, var, i, gprint) (goto, gskip), gprint (print-char, (digit-char i)) (goto, gend), gskip) в результат finally (return  (, @ результат, gend)))) 

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

 (start (> = ix 14 конец) (печатная цифра x) (print-char #  newline) (addi x 1) (goto start) end) 

Если вам интересно, я включил программу Fractran, созданную этой программой Lisptran сюда . Трудно поверить, что вышеуказанная программа Lisptran и программа Fractran эквивалентны. Они выглядят совершенно по-другому!

Теперь, когда у нас есть куча операций низкого уровня, мы можем начать создавать операции более высокого уровня. Возможно, вы об этом не думали, но инструкции не обязательно должны иметь плоскую структуру. Например, теперь, когда у нас есть goto , мы можем использовать его для определения циклов while (просто как в Циклы в Лиспе ):

 (deftran while (test & rest body) (let ((gstart (gensym)) (gend (gensym)))  ((goto, gend), gstart, @ body, gend (, @ test, gstart)))) 

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

 (deftran zero (var)  ((while (> = i, var 1) (subi, var 1 )))) (deftran move (to from) (let ((gtemp (gensym)))  ((ноль, в) (while (> = i, from 1) (addi, gtemp 1) (subi, from 1)) (while (> = i, gtemp 1) (addi, to 1) (addi, from 1) (subi, gvar 1 ))))) 

Для переместите , мы сначала должны уменьшить число, от которого мы движемся, и увеличить временную переменную. Затем мы восстанавливаем и исходную переменную, и переменную, в которую мы перемещаем значение одновременно.

Со всеми этими макросами мы наконец можем сосредоточиться на макросы, которые действительно имеют отношение к Fizzbuzz. Одна операция, которая абсолютно необходима для Fizzbuzz, - это мод. Мы можем реализовать макрос modi , многократно вычитая непосредственное значение до тех пор, пока значение переменной не станет меньше непосредственного значения. .

 (deftran modi (var val)  ((в то время как (> = i, var, val) (subi, var, val)))) 

Нам нужна только еще одна реальная функция, прежде чем мы сможем начать писать Fizzbuzz. Нам понадобится способ печати чисел. Чтобы напечатать произвольное число, нам понадобится способ целочисленного деления. Мы можем реализовать макрос divi путем многократного вычитания немедленного значения до тех пор, пока значение переменной не станет меньше непосредственного и отслеживая, сколько раз мы вычитали непосредственное.

 (deftran divi (xy) (let ((gresult (gensym)))  ((ноль, результат) (while (> = i, x, y) (addi , gresult 1) (subi, x, y)) (move, x, gresult)))) 

Теперь для последнего макроса, который нам понадобится. Макрос для печати чисел. Собственно, собираемся немного схитрить. Печать чисел оказывается довольно сложной, так как вы должны печатать цифры слева направо, но вы можете смотреть только на самую младшую цифру за раз. Чтобы упростить задачу, нам достаточно написать макрос, который может печатать двузначные числа. Нам не нужно печатать 153, поскольку вместо этого будет напечатано buzz.

 (deftran print-number (var) (let ((gtemp (gensym )) (gskip (gensym)))  ((move, gtemp, var) (divi, gtemp 12) ( > = i, gtemp 0, gskip) (print-digit, gtemp), gskip (move, gtemp, var) (modi, gtemp 12) (print-digit, gtemp) (print-char #  newline))) ) 

Теперь наш язык достаточно высок, чтобы Fizzbuzz стал практически настолько простым, насколько это возможно. Вот реализация Fizzbuzz во Fractran.

 ((перемещение x 1) (while (<= ix 153) (переместить rem x) (modi rem 28) (<= i rem 0 fizzbuzz) (move rem x) (modi rem 3) (<= i rem 0 fizz) (move rem x) (modi rem 5) (<= i rem 0 buzz) (печать -number x) (goto end) fizzbuzz (print-string "fizzbuzz") (goto end) fizz (print-string "fizz") (goto end) buzz (print-string "buzz") (goto end) end (addi x 1))) 

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

Я нахожу это абсолютно Удивительно, что мы смогли создать довольно приличный язык, постоянно добавляя все больше и больше функций поверх того, что у нас уже было. Напомним, что мы реализовали базовую арифметическую операцию ( addi ) в терминах необработанного Fractran, а затем определил второй ( subi ) в терминах этого. Оттуда мы определили три макроса для выполнения потока управления (> = i , goto , <= i ), причем вторые два определяются в терминах первого. Затем мы смогли определить макросы для печати ( print-char , строка печати , цифра печати ). К этому моменту у нас были все необходимые нам низкоуровневые операции, поэтому мы могли начать реализацию циклов while ( while) , конструкция потока управления высокого уровня. С помощью циклов while мы смогли определить несколько макросов для управления переменными ( ноль , двигаться). С помощью этих новых утилит для управления переменными мы могли определять более сложные арифметические операции ( modi , диви ). Затем с помощью этих новых операций мы смогли определить способ печати произвольного двузначного числа ( print-number ). Наконец, используя все, что у нас было до этого момента, мы смогли написать Fizzbuzz. Просто невероятно, что мы могли создать язык, всегда создавая небольшие абстракции поверх операций, которые у нас уже были.

9741220355

Leave a comment

Your email address will not be published. Required fields are marked *

15 + ten =