Comments 58
let же может быть тривиально реализован как макрос — например
(let (a (+ 1 2 3)
b (* 4 5 6))
(foo (+ a b)))
раскроется как
((lambda (a b) (foo (+ a b))) (+ 1 2 3) (* 4 5 6))
и для этого не нужно ничего в ядре языка, макросы позволяют навернуть на базовых возможностях все что угодно. let* так же тривиально макросом реализовать, только там будет разворачиваться не в одну мультиарную лямбду, а в несколько вложенных унарных — для гарантии порядка вычисления аргументов, если он не гарантируется в колл-конвеншене вызова (как в Scheme, например).
let же может быть тривиально реализован как макрос — например
let действительно легко реализуется, но core-форма обычно letrec, и тут уже есть проблемки.
Я понимаю, что есть миллион диалектов сабжа, но не верю, что среди них есть такие запущенные )
- Если нет макросов — напиши свой макропроцессор. Гомоиконность, как-никак. Пишешь кот, квотишь его, скармливаешь функции преобразования, получаешь другой кот, копипастишь результат куда надо.
- Про императивный стиль — и сейчас есть широко распространенные языки, в которых нет мутабельности от слова совсем. И ничего, живут как-то люди.
- Defun — это (внезапно) тоже макрос ) То есть
(defun (f a b) (+ a b))
раскрывается в
(def f (lambda (a b) (+ a b)))
- Про подозрения — не постиг смысл и идею что у вас там написано, но есть подозрение, что вы хотите накостылить семантику let без макросов через лямбды. Работать будет только если квотировать в месте использования конструкции каждый раз — потому что у лямбды аппликативная редукция в отличие от нормальной у макросов (по-русски, лямбда форсит вычисление своих аргументов, а макрос — нет), и результат придется еще эвалить — только не говорите, что в вашем диалекте не было eval )
Да, я думал именно наколхозить механику let/set через лямбды. Формально решение должно существовать, если макрос является строгой Лисп-функцией, и может отсутствовать, если прикручен извне.
Про императивность — меня учили не разбрасываться памятью, и в 1996м сборка мусора была в зачаточном состоянии, ещё как парадигма только ЕМНИП, как следствие, иммутабельными могли быть только константы, иначе программа быстро исчерпывала память. А так — если прописывать, что при вызове функции под результат каждый раз выделяется память, которую средствами языка потом нельзя поменять, why not.
Решил побаловаться — написал лет через функцию. Только квотировать при вызове надо, как выше говорил
(defn let (l)
(defn go (l ns es)
cond (null? (cdr l)) (cons (cons 'lambda ns l) es)
(go (cdr l) (cons (caar l) ns) (cons (cadr (car l)) es)))
((lambda (x) (print "macroexpand:" \n x \n) (eval x))
(go l nil nil)))
(let '((a (+ 1 2 3))
(b (* 4 5 6))
(c (* 7 8 9))
(+ a a b b c c)))
=> macroexpand:
((lambda (c b a) (+ a a b b c c)) (* 7 8 9) (* 4 5 6) (+ 1 2 3))
1260
Так же следует обратить внимание на язык Racket (это развитие Scheme/lisp). По сути это полигон развития языкостроения
Так же следует обратить внимание на язык Racket (это развитие Scheme/lisp). По сути это полигон развития языкостроения
Стоит отметить, что некий небезызвестный typescript построен в значительной части именно на базе научной работы команды Racket.
И яп для упомянутого Last Of Us тоже именно на Racket писались (с-но, в Last Of Us, как и в Uncharted, использовался не один язык, а много специализированных диалектов).
То есть, в смежных с программированием областях, нужда в неких DSL есть и использование лиспа позволяет достаточно просто такую задачу решить.
Поклонники Racket особенно плодовиты, там даже шутка у них есть, что в неделю до 8-ми языков изобретают.
Например согласно этому опросу/исследованию JVM экосистемы от JavaMagazine, (более 10к респондентов разработчиков) Clojure занимает второе место после Java среди языков использующих JVM платформу.
Не может не радовать что в наши дни современные диалекты как Racket и Clojure имеют такое широкое применение.
Собственно счастлив тем, что использую Clojure ежедневно в работе как Clojure разработчик.
Автору спасибо за статью. Было интересно читать.
Так что вы, являясь членом Ruby коллектива, фактически и так пишете на lisp, потому вам и кложа приятна :)
Руби негомоиконный, что, с-но, ставит крест на полноценном метапрограммировании.
Ну и нормальной модели compile-time'а в рубях нет. Хотя, справедливости, ради она и в лиспах только в scheme-derived диалектах есть.
модели compile-time'а
Что вы имеете под этим в виду? SBCL'евское save-lisp-and-die
не про это?
(Я сам мимо проходил только Scheme немного знаю).
Примерно вот это:
https://docs.racket-lang.org/reference/eval-model.html#%28tech._phase%29
В CL все как раз плохо с этим.
Что Вы понимаете под «полноценным метапрограммированием»?
Что Вы понимаете под «полноценным метапрограммированием»?
Это когда можно метапрограммировать легко и просто, в этом случае метапрограммирование становится стандартным способом решения проблем, а написать макрос/микродсл — то же самое, что написать простую функцию (сравните с подходом, когда: "пишем макросы только в том случае, если обычных языковых средств не хватило"). Данное требование накладывает на систему метапрограммирования ряд условий:
- Гомоиконность
- Понятная и строгая семантика рантайма/компайлтайма
- Наличие в стандартной библиотеке возможностей парсинга и генерации экспрешшенов (паттерны, квазицитирование)
- Понятная и строгая модель связывания, встроенные средства работы со связыванием, с контекстами (сюда же входит наличие гигиены, как свойства макропреобразования сохранять альфа-эквивалентность, и гибких средств обхода этой гигиены)
- Наличие средств обработки ошибок во время макроэкспанда (например, вывод точной позиции в строке и столбце, возможность контроля со стороны того, кто пишет макрос)
можете сами посчитать, скольки пунктам удовлетворяет руби.
Если в сухом остатке — метапрограммировние есть, полноценного нет.
Я сначала писал 16 лет на Java, потом сьехал на Scala, потом попробовал Clojure а потом переехал в Австралию и два года пишу на Ruby :)
Собcтвенно игра Jax & Daxter написана на нём чуть ли не целиком.
en.wikipedia.org/wiki/Game_Oriented_Assembly_Lisp
Соответственно вместо «кронциркуль или компас» должно быть «циркуль или кронциркуль»
![](https://habrastorage.org/webt/qf/ol/b8/qfolb85kgpa9qcyrcb0vavibipa.png)
Лисп-машины, придуманные в неудобный момент в конце эры мини-компьютеров, но до начала расцвета микрокомпьютерной революции, были высокопроизводительными персональными компьютерами для элиты программистов.
…
В год выхода SICP Бьёрн Страуструп опубликовал первое издание книги «Язык программирования C++», принёсшей ООП в массы. Несколько лет спустя рынок Лисп-машин рухнул
Было бы очень интересно прочитать, что такое Лисп-Машины, и чем они столь радикально отличались от обычных компьютеров.
Если очень коротко, в процессор (реализующий принцип CISC) лисп-машин был встроен программно-аппаратный модуль поддержки сборщика мусора (ускорение сборки мусора было до 1000 раз), а так же возможность обычному пользователю добавлять новые машинные инструкции в процессор.
Но потом, в 80-х, появились микро-компьютеры и RISC процессоры которые стоили дешевле на порядки и работали при этом достаточно быстро покрывая нужды большинства программистов.
Было бы интересно видеть в статье пометку: а сколько же это "гора памяти" в те времена.
Заметку сделать вот в этом месте:
В августовском номере от 1979 года, посвящённом Лиспу, главные редактор восторгался новыми машинами, разрабатываемыми в MIT, «с горой памяти» ( XXXКбайт, — прим. ред.)
habr.com/post/190082
В 1988 эта же компания представила миру линейку Ivory, первым представителем которого был XL400. Эти же компьютеры были уже 40-битными и позволяли адресовать 16ГБ памяти
А ведь обычным программам тогда более чем хватало 640kb…
Лисп классный, но скобки перед функцией меня убили. Так и писал:
add ( 2 *мат* backspace backspace ctrl+left ( ctrl+right 2 3 )
:(
Ну все есть функция, ок. А что можно быстро и легко сделать на лиспе и что потребует ощутимо больше усилий, например на питоне или джаваскрипте?
Поэтому сейчас довольно трудно сравнить. Всё течет, всё меняется.
Как Lisp стал языком программирования для Бога