LINUX.ORG.RU

[Lisp]как препроцессор для C - 2

 


0

0

Вот тут http://www.linux.org.ru/view-message.jsp?msgid=3080272#3088381 ответили, старая тема не работает. Поэтому заводим новую.

Отвечаю на Схему. Лисп, как известно, динамически типизирован. Это ведёт к проблемам с производительностью. И если в sbcl за счёт тщательной работы с декларациями типов почти удаётся догнать С (а иногда и обогнать), то у Схемы нет никаких шансов, т.к. декларации типов в ней не предусмотрены. Это - первое. Второе: сборка мусора. Совсем не подходит для многих задач, и тоже замедляет работу во многих случаях. Третье: иногда программа должна быть маленькой. Например, это может быть программа, которая неряшливо открывает файлы. Чтобы их закрыть, достаточно убить программу, тогда операционная система закроет файлы сама. Эту программу мы будем вызывать из лиспа. Или нужна программа для мобильного устойства, где мегабайт рантайма - это уже много. А мощная схема имеет как раз примерно такой рантайм.

В том, что я хотел сделать, всё гораздо проще - от лиспа берётся синтаксис, от С - семантика. Т.е., к примеру, такие вещи, как GObject (сам не видел, но говорят, там нужно много копипастить) становятся (в идеале) лёгкими в обращении за счёт метапрограммных средств лиспа.

Лисп характерен тем, что в нём есть естественное представление AST и множество механизмов (удобной) работы с ними. Вот этим и предлагается воспользоваться. Потому что от синтаксиса С до AST - расстояние очень небольшое.

Вот есть, к примеру, проект opencxx. Но cxx меня в принципе не интересует, т.к.... ладно, молчу. В общем, не интересует. Поэтому, целевой проект - гораздо проще - openc. C с промежуточным слоем в виде AST. AST представлены и трансформируются в лиспе. А чтобы не мучаться с транслятором C->AST, мы просто сразу пишем в терминах AST, поскольку верим, что это удобнее.

Такой проект вполне реализуем одним человеком за ограниченное время. В общем-то, я уже реализовывал работоспособную и отчасти даже полезную часть такого проекта для скриптовых языков SQL, на которых пишутся хранимые процедуры. Из-за половинчатого подхода к реализации (генерил я всё же текст, а не AST) получилось не очень хорошо. Впрочем, если пытаться SQL перевести на AST а потом на этом AST ещё и разрабатывать прикладной код, вполне можно повредиться умом. Поэтому именно для SQL возможность генерации именно текста, а не AST, как раз кажется более верной (хм, кстати, это забавная мысль...) В итоге я получил SQL с макросами и уложился в 100 кб исходников самого генератора. И (при должной степени фанатизма) можно убедить себя в том, что внедрение этого генератора было скорее полезно, чем вредно.

Реализовать же компилятор со схемы или (боже упаси) CL на С - это значит столкнуться с массой проблем либо перетащить проблемы лиспа в новое приложение на С. Даже применять уже существующий компилятор - это тоже трабл. Приложения лиспа тяжеловесны и стабильны. Приложения на С - легки и часто падают. Попытка их скрестить именно в грубом физическом смысле слова, бездуховно, приводит к сочетанию худших качеств: приложение получается тяжёлым и падучим. А вот если применить святой дух (барьер метатрансформации или хотя бы взаимодействие через IPC), то тогда уже получается лучше.

★★★★★

А смысл в таком вот? Без сборки мусора невозможны функции высокого порядка, так что 3/4 возможностей лиспа теряем. Если сильно беспокоимся за память, то в лиспе есть такая вещь, как dynamic-extent (по сути, это аллокация объектов в стеке, а не куче), которую sbcl поддерживает.

Если нужен малый рантайм, то можно сделать image для sbcl, где не будет ничего лишнего. Или воспользоваться ECL (у него, AFAIK, небольшой рантайм).

mo3r
()
Ответ на: комментарий от bik

это была шутка. у меня за долгое время использования чикена, и в последнее время - гамбита - оно не падало ни разу. что я делаю не так? :-)

ott ★★★★★
()
Ответ на: комментарий от ott

> Если нужен малый рантайм, то можно сделать image для sbcl, где не будет ничего лишнего.

Буду рад услышать про Ваш опыт в этом направлении. У меня тоже есть предположение, что такое должно быть возможно, но вот здесь http://shootout.alioth.debian.org/u32q/benchmark.php?test=all&lang=sbcl&a... нигде объём памяти получается не меньше 5Мб. Для С соответствующие объёмы памяти - 400кб. Согласитесь, для мобильных систем разница будет более чем ощутима.

> Без сборки мусора невозможны функции высокого порядка, так что 3/4 возможностей лиспа теряем

Хм. Рассмотрим на примере mapcar. Функции высшего порядка основываются на следующих возможностях:

1. Передача функции в качестве аргумента. Можно в С.

2. Замыкания. lambda, вообще говоря, каждый раз порождает новый объект и его нужно когда-то удалить. Что нам мешает ввести в синтаксис/семантику языка возможности для этого? Например, если написано (mapcar (lambda ...) ...), то заведомо известно, что после выхода из mapcar данная lambda больше не нужна и её можно разместить на стеке.

Если же написано (return (lambda ...)) то можно ввести ключевое слово :allocation. (lambda (args) (:allocation :heap) "docstring" ...). Но можно и компилятор заставить вычислять allocation в зависимости от вызова, в который падает return (имея на самом деле две версии концовки функции).

3. mapcar порождает свежий список. Ну и что? Мы можем либо задать список-приёмник в качестве третьего аргумента mapcar, либо сделать аргумент :allocation, либо тупо всегда порождать результат в хипе.

Я ответил на Ваш вопрос или что-то ещё упустил?

> а авторы chicken & gambit scheme про кучу траблов знают?

Авторы - наверное, нет. С прикладными пользователями я не общался. ИМХО схема слаба для индустриального программирования. Пару причин я уже писал. И сам язык слабее. Пространств имён в стандарте нет. Даже такая вещь, как keyword arguments, в ней является расширением базового языка. То же можно сказать про классы и исключения. Макросы - намного более ограниченные, чем в CL. Возможно, я неправ, но ИМХО писать на схеме переносимые (между реализациями схемы) достаточно содержательные программы точно не получится, а непереносимые, скорее всего, будут писаться с бОльшим трудом, чем в случае CL. И если CL можно пытаться позиционировать как замену Java, то схему - нельзя (разве только как фронтенд к яве, но и это не выйдет из-за отсутствия в яве полноценных continuations).

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

> Например, если написано (mapcar (lambda ...) ...), то заведомо известно, что после выхода из mapcar данная lambda больше не нужна и её можно разместить на стеке.

(define mapcar (lambda (...) ...))

и наш mapcar сохраняет переданную ему лямбду в глобальной переменной (не знаю, зачем, кеширует чего-нибудь, например). Тогда нельзя её на стеке размещать.

Legioner ★★★★★
()

> Лисп, как известно, динамически типизирован. Это ведёт к проблемам с производительностью.

интуитивно согласен, да и Smalltalk/Strongtalk это косвенно подтверждает. Однако, хотелось бы каких-то количественных оценок, набора тестов, на которые можно было бы проверить конкретное приложение. "Ведёт это к проблемам" или hotspot совсем не в этом.
Потом, там в работе описывается "подмножество Common Lisp", которое относительно удобно может быть отображено на С. Это подход скорее с другой стороны, выбрать минимальный лисповый API, оверхед от динамичности которого может быть минимален. Правда, лисповые списки транслируются всё равно в массив структур с тегами, которые больше напоминают cons ячейки какого-нибудь встраиваемого PicoLisp, чем отдельную систему С-типов.
Здесь предлагается что, ещё более ограничить "лисп-подмножество", оставив из него только макры?

"Лисп динамически типизирован", в итоге есть однородность работы с разными типами данных, универсальное представление кода как данных, преобразования макросами, совместимость функций по прототипам = примененимость функций высокого порядка. С жестко закодированными прототипами преобразования не будут такими простыми: мы хотим иметь "абстрактный AST", а не "типизированный AST", потому что преобразовывать хочется не жестко заданным С/С++ "интерфейсом работы с AST, вызываемым компилятором", а чем-то более гибким на лиспе (то есть строим прототип такого интерфейса "с другого конца"). Что при этом происходит с аннотациями функций, при преобразованиях?

"Аннотации " и "декларации типов", по идее могут быть выводимы через type inference и/или дополнительно что-то может выясниться для оптимизации. Через аннотации как C/C++ интерфейс пытался работать проект http://www.dreamsongs.com/Cadillac.html в составе Emacs, интересное чтиво http://www.dreamsongs.com/Files/Energize.pdf . Хотя это "описание архитектуры C++ IDE", но в итоге все отдельные инструменты-программы в её составе работают с единым AST.

> В том, что я хотел сделать, всё гораздо проще - от лиспа берётся синтаксис, от С - семантика.


насколько я вижу, есть как минимум 2-3 подхода:
1. Встраиваемый лисп в C/C++/D приложение, рантайм с обёртками для представления С типов в лисп коде и наоборот. Или связка FFI/C, но тут не нравится "толстый лисповый рантайм". Во встраиваемых реализациях вроде ECL/PicoLisp/Dlisp рантайм сильно меньше.
2. Трансляция лиспового кода в С, как в той ссылке, компиляция С компилятором. Пишем трансформации на псевдолиспе, который переведется в С код. Исполняется С-код с С-рантаймом. Что не нравится в этом подходе, "перетащить проблемы лиспа в новое приложение на С"? Что это за проблемы и в чём их причина?
3. Трансляция С кода в лисп, исполняется лисп лисповым рантаймом. "Не устраивает рантайм" :))

> Т.е., к примеру, такие вещи, как GObject (сам не видел, но говорят, там нужно много копипастить) становятся (в идеале) лёгкими в обращении за счёт метапрограммных средств лиспа.


для GObject есть, например, Vala. Как транслятор из своего языка в С она не очень интересна, интересна внутренняя остнастка о накладных расходах на реализацию препроцессора, уровнем выше простого препроцессора-кодогенератора.

> Но cxx меня в принципе не интересует, т.к.


думается, на D что-то аналогичное OpenC++ можно сделать D шаблонами во время компиляции :)) например у ЕМНИП MiniD и Dlisp сделан парсер/лексер языка шаблонами D времени компиляции

anonymous
()

<продолжение>
> Вот есть, к примеру, проект opencxx.


ага, или довольно странный проект

http://www.conman.org/software/amc/docs/html/amc_usersguide-1.html#ss1.1

http://www.conman.org/software/amc/ . "Реализация модульности Turbo Pascal в Си +

любопытные расширения вроде map"
или проект Harmony в Berkley, но он похоже слишком всеохватен.


> Поэтому именно для SQL возможность генерации именно текста, а не AST, как раз кажется


более верной (хм, кстати, это забавная мысль...)

согласен, SQL исполняет текстовые запросы, так что от AST там ни холодно ни жарко. Хотя

если дерево AST переводится в набор подзапросов, то.. (это будет неэффективный запрос)

>Приложения лиспа тяжеловесны и стабильны. Приложения на С - легки и часто падают. Попытка


их скрестить <..> приводит к сочетанию худших качеств: приложение получается тяжёлым и

падучим

почему С-приложение "падуче"? из-за неконтролируемого обращения с указателями? И хочется

чего-то вроде http://www.st.cs.uni-sb.de/edu/seminare/2005/advanced-fp/docs/sweeny.pdf ,

только не на хаскелле, а на лиспе?

> А вот если применить святой дух (барьер метатрансформации или хотя бы взаимодействие


через IPC), то тогда уже получается лучше.

то есть, "MOP на C++" или "MOP на С" или метатрансформацию исходников?

Пока не определены место и задачи такой метатрансформации, её интерфейс, единственная идея

возникает "встроить лисп"

итого, возвращаясь к постановке задачи, чем не нравится "встраиваемый лисп" в каком-либо

виде?

накладными расходами в рантайме на трансляцию списков параметров функций С и Лисп?

хочется отложить это на трансляцию времени компиляции чтобы получить готовое отображение

"лисп функции"-"си функции" компилятором?

anonymous
()
Ответ на: комментарий от Legioner

>(define mapcar (lambda (...) ...))

>и наш mapcar сохраняет переданную ему лямбду в глобальной переменной (не знаю, зачем, кеширует чего-нибудь, например). Тогда нельзя её на стеке размещать.

static переменная в Cи функции?

anonymous
()
Ответ на: комментарий от anonymous

> есть, например, Vala. Как транслятор из своего языка в С она не очень интересна, интересна внутренняя остнастка о накладных расходах на реализацию препроцессора, уровнем выше простого препроцессора-кодогенератора.

то есть, Vala была бы интереснее любого другого "кодогенератора в духе MFC-wizard'ов" или "кодогенератора на Motif", если бы кроме gcc -E умела бы не только (macroexpand-1 vala-code) но и gpprof в терминах vala-code, а не развёрнутого препроцессором

anonymous
()

>Я-то хочу сделать так, чтобы (я в начале уже писал)
>(c:defun int fact ((int n)) ...)

>превращался конкретно в

>int fact(int n) { ... }


вообще пример FFI для SBCL отсюда
http://www.lisphacker.com/blog/display/some-sbclwin32-hacking довольно понятный:
в lisp-winapi.tgz единственное мутное место, это генерация лисповых и ассемблерных обёрток для Win32 в callback-hacking.lisp.

Сдаётся, что если генерировать не исполняемый ассемблерный сегмент, а исходник на Си, и если переписать в более общем виде для всех Си функций (для пущей кроссплатформенности), а не возиться со стеком и прототипом, это будет ближе к теме.

anonymous
()
Ответ на: комментарий от anonymous

если взять этот исходник как "тестовый пример" для будущего препроцессора.
Написать аналогичный код на Си.
Переделать реализацию callback-hacking.lisp для генерации Си прототипов функций.
И посмотреть, как должен на этом примере выглядеть "препроцессор на лиспе".

Ну а потом переписать пример с winapi на Gtk, например.

anonymous
()
Ответ на: комментарий от anonymous

вот например в lisp-hello-win32.tgz в hello.lisp в window-procedure вызывается через FFI
DrawTextW. Оно описано в lisp-winapi.tgz в win32-types-and-costants.lisp в конце, напрашивается какое-то общее описание абстрагирующее прототип FFI функции, чтобы не писать (extern-alien "DrawTextW" (function int hdc (* unsigned-short) int (* (struct rect)) unsigned-int)) по два раза и там и там. Напрашивается макрос

Сам win32-types-and-costants.lisp его часть с прототипами функций по идее тоже может генерироваться макрами из Cшного windows.h или как его там, чтобы прототипы Си функций автоматически генерировались правильные, лисповые из Си прототипов.

Ещё в hello.lisp register-window-class напрашивается на рефакторинг макрами, макра с какими-то именованными параметрами вместо (setf (slot wndclass 'paramname) (value-list) .

Допустим, раскручиваем пример таким образом:

1. Всё реализовано на лиспе, файл с прототипами "захардкожен"
1.1. Лисповый код "отрефакторен" макрами, файл с прототипами (win32-types-and-costants.lisp) генерируется какой-то тулзой, которая понимает windows.h с прототипами на Си и генерирует "прототипы на лиспе". "Прототипы на лиспе" завёрнуты в какую-то макру, чтобы не писать этот развесистый (function-alien "XXX" (c:defun..))
2. написан аналогичный код на "голом Си" (то, что должен выдать "препроцессор на лиспе")
3. на (defun window-procedure ..) натравливается функция высшего порядка (вместо eval.. например, emit-c), которая на встреченную лисп-форму выдаст Си-statement, который делает то же самое.

Макра, в которую заворачивается вызов "прототипов на лиспе" заменяется на прямую подстановку "прототипа на Си".

если тулза, понимающая windows.h сама на лиспе, можно просто сделать хеш-таблицу "Си прототип из windows.h" => "прототип на лиспе" , которую тут использовать.
4. сравниваем выданный Си код с написанным руками в 2
5. "Препроцессор": добавляем лисповые макры, новые определения Си функций и пишем юнит-тесты, что полученный код разворачивается в один и тот же Си-код. В крайнем случае, можно проверить как должно работать определение си-функций макрами в лисповом коде, по аналогии с генерацией прототипов из windows.h.
6. Переписываем Си пример на что-то более разумное, например, Gtk :))

anonymous
()
Ответ на: комментарий от anonymous

Мда, а обратная задача "генерировать лисповое промежуточное представление из С/Basic/etc исходника" похоже чуть сложнее: надо разбирать грамматику входного языка. Думается, что-то можно сделать reader макрами, но как быть если семантика предложения становится ясна только после прочтения предложения целиком, то есть "длина предложения" в разных предложениях разная? Вводить какой-то искусственный "конец предложения", как ) в лиспе?

anonymous
()

> то у Схемы нет никаких шансов, т.к. декларации типов в ней не предусмотрены

Посмотри на Сталина.

> Это - первое. Второе: сборка мусора. Совсем не подходит для многих задач, и тоже замедляет работу во многих случаях.

Сборка мусора бывает разной. И нагрузка на GC зависит от стиля кодирования. Даже в Java c её кривеньким GC можно жесткого реального времени добиться.

> Реализовать же компилятор со схемы или (боже упаси) CL на С - это значит столкнуться с массой проблем либо перетащить проблемы лиспа в новое приложение на С.

Ты уже прочитал "Компиляция Схемы в Си за 90 минут"? http://lambda-the-ultimate.org/node/349

anonymous
()
Ответ на: комментарий от mo3r

> так что 3/4 возможностей лиспа теряем

Функциональщина - это 1/100 возможностей Лиспа. 99/100 - это (defmacro ...)

anonymous
()
Ответ на: комментарий от den73

> Например, если написано (mapcar (lambda ...) ...), то заведомо известно, что после выхода из mapcar данная lambda больше не нужна и её можно разместить на стеке.

Осторожно! Ты пытаешься переизобрести банальный region analisys. Лучше велосипедов не строить и сделать всё, как классики учат.

> Пространств имён в стандарте нет.

R6RS уже несколько месяцев как стандарт.

> И если CL можно пытаться позиционировать как замену Java, то схему - нельзя (разве только как фронтенд к яве, но и это не выйдет из-за отсутствия в яве полноценных continuations).

С промышленным использованием SISC я сталкивался.

anonymous
()
Ответ на: комментарий от anonymous

Круто, много написали! Всем спасибо, но, извините, я работаю :((( Так что отвечаю не на всё. Анонимусов прошу зарегиться.

> макросы в схеме часто намного мощнее чем в cl - и это сами лисперы часто признают...

Можно пару примеров?

> Однако, хотелось бы каких-то количественных оценок, набора тестов, на которые можно было бы проверить конкретное приложение

См. мой предыдущий пост со ссылками на тесты. Заодно можете посмотреть лисповый код тестов. Увидите, что все типы задекларированы. Единственное, что там все приложения просты. Может быть, при более сложных разница будет выглядеть по-другому.

> Здесь предлагается что, ещё более ограничить "лисп-подмножество", оставив из него только макры?

Примерно так. Плюс ещё базовые конструкции, касающиеся определения функций и управления вычислениями. Ну и, может быть, подмножество CLOS. Насчёт консов - не знаю. В идеале, желательно, чтобы работа с памятью была декларативной (как сейчас декларативна в лиспе работа с типами).

> у меня за долгое время использования чикена, и в последнее время - гамбита - оно не падало ни разу.

Поздравляю. А FFI был? Ну и кстати, какой получается рантайм по расходу ОЗУ?

> в lisp-winapi.tgz единственное мутное место, это генерация лисповых и ассемблерных обёрток для Win32 в callback-hacking.lisp

Для этого есть некие средства (например, www.swig.org), но конкретно windows.h может оказаться непростой задачей. Тем не менее, я думаю, она уже кем-то решена и можно позаимствовать. Даже если нет, то можно преуспеть, если не пытаться сразу решить всю задачу целиком. Впрочем, я пока что не очень заинтересован в создании нативных приложений win32.

Касаемо Вашего плана - я бы тестировал всё же функциональную идентичность: пишется некая програ на подмножестве лиспа и тесты к ней. Далее это подномжество лиспа транслируется на С и на неё напускаются те же самые тесты. Потому что код на С вряд ли будет слишком уж компактным - тяжело будет его написать руками.

Если Вы реально заинтересованы поучаствовать, зарегьтесь или просто напишите на мыло. Сейчас нет ресурсов смотреть код и разбираться в сути Вашего предложения. И боюсь, в ближайшие 2-3 месяца проект не будет двигаться - я занят.

> надо разбирать грамматику входного языка

Нужно просто взять парсер. Наверняка их полно. Уж для С - точно есть.

den73 ★★★★★
() автор топика
Ответ на: комментарий от ott

> макросы в схеме часто намного мощнее чем в cl - и это сами лисперы часто признают...

Алекс, не гоните глупостей. Гигиенические схемские макры средствами Лиспа реализовать тривиально. А вот реализовать полностью Лисп средствами схемских макр нельзя - семантику ещё худо-бедно (через задницу) можно изобразить, но вот доступа ко всей огромной библиотеке функций уже не будет.

В качестве примера - на CL можно сделать макру, которая прочитает конфигурацию из файла, имя которого передано в аргументе, и породит соответствующий код. Можно сделать макру, которая полезет в базу данных. На Схеме, стандартной, без расширений (R5RS и раньше), такое просто невозможно.

anonymous
()
Ответ на: комментарий от anonymous

> Мда, а обратная задача "генерировать лисповое промежуточное представление из С/Basic/etc исходника" похоже чуть сложнее: надо разбирать грамматику входного языка.

Из Си - сложно, очень. У него синтаксис паршивый, нужен или полноценный GLR с разрешением противоречий после этапа type propagation, или грязные танцы с патченьем лексера во время работы (как большинство промышленных C/C++ парсеров и делает).

А вот из более вменяемого синтаксиса (те же Бейсик, Паскаль) - тривиально. Пример тут пробегал: http://lambda-the-ultimate.org/node/2895

Там же есть и обратный пример - встроенный в Лисп низкоуровневый язык, который можно использовать как целевой язык для макр.

> Думается, что-то можно сделать reader макрами

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

anonymous
()
Ответ на: комментарий от den73

> Нужно просто взять парсер. Наверняка их полно. Уж для С - точно есть.

Святая простота... Как раз для Си (и тем более C++) с этим делом всё очень паршиво.

anonymous
()
Ответ на: комментарий от den73

> Анонимусов прошу зарегиться.

Лучший способ лишиться доступа к бесконечной копилке знаний Анонимных Аналитиков ЛОРа.

anonymous
()
Ответ на: комментарий от den73

>Функции высшего порядка основываются на следующих возможностях: >Я ответил на Ваш вопрос или что-то ещё упустил?

Упустили. ФВП основаны еще на возможности создавать новые функции по ходу работы.

(defun make-adder (n) (lambda (x) (+ n x)))

Основная проблема не с самой лямбдой, а тем, что она использует переменные из своего лексического окружения, и неизвестно, кто и когда их высвобождает. И получается, что без сборки мусора мы должны ограничить распределением на стеке или ручным освобождением (что практически уничтожает всю суть). Ну и кому это надо?

mo3r
()
Ответ на: комментарий от anonymous

>Мда, а обратная задача "генерировать лисповое промежуточное представление из С/Basic/etc исходника" похоже чуть сложнее: надо разбирать грамматику входного языка.

Разбор грамматики — это вообще мелочь, по сути (если язык не какой-нибудь монстр типа C++, для C — довольно просто). Главное — правильный перенос семантики одного языка на семантику другого. SWIG этим и занимается, и вполне неплохо.

mo3r
()
Ответ на: комментарий от mo3r

> Основная проблема не с самой лямбдой, а тем, что она использует переменные из своего лексического окружения, и неизвестно, кто и когда их высвобождает... И получается, что без сборки мусора мы должны ограничить распределением на стеке или ручным освобождением (что практически уничтожает всю суть)

Я не согласен, что ручное освобождение практически убивает всю суть. Море полезных программ написано без сборки мусора, хотя сложно спорить, что она очень удобна. Лично мне не так уж часто бывают нужны лямбды, которые используются нелокально. Скорее они используются именно как локальные функции. В случае же возвращения лямбды из функции можно выделить её в куче и возложить заботы об её освобождении на вызывающую сторону.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

>Я не согласен, что ручное освобождение практически убивает всю суть. <..> В случае же возвращения лямбды из функции можно выделить её в куче и возложить заботы об её освобождении на вызывающую сторону.

главное, чтобы интерфейс выделения памяти был консистентный, а не как BSTR строки в COM. Там штук 5 наверно функций с разными соглашениями кто кому выделяет память, и кто за кем освобождает; сколько памяти надо выделить и как об этом сообщить клиенту и т.п. Зоопарк.

Убивает наверно не "всю суть", что-то можно завернуть в макры и использовать типа RAII, но большинство удобства идеи.

Я вот недавно скачал посмотрел BEE LISP под оффтопик, примеры win32 приложений на лиспе, написанные в худших традициях Си. Мегалол возник, когда обнаружилось 1) не реализованы макры, и код вроде инициализации структур идет копипаст setf слот в структуре с изменениями 2) увидел malloc/free (и пул объектов в инициализации) . В программе на Лиспе.

anonymous
()
Ответ на: комментарий от anonymous

Так, в общем, я вряд ли буду поддерживать эту тему в ближайшее время. См. новую тему.

den73 ★★★★★
() автор топика

> у Схемы нет никаких шансов

Авторы stalin знают про это?

anonymous
()
Ответ на: комментарий от anonymous

> в Java c её кривеньким GC можно жесткого реального времени добиться.

Кому это уже удалось?

tailgunner ★★★★★
()
Ответ на: комментарий от Legioner

>> Например, если написано (mapcar (lambda ...) ...), то заведомо известно, что после выхода из mapcar данная lambda больше не нужна и её можно разместить на стеке.

> (define mapcar (lambda (...) ...))

> и наш mapcar сохраняет переданную ему лямбду в глобальной переменной (не знаю, зачем, кеширует чего-нибудь, например). Тогда нельзя её на стеке размещать.

Все просто. На это нужен атрибут к лямбде типа "не собирать как мусор, запретить складывать куда-то и иметь в единственном экземпляре", аналог с++ auto_ptr<> (но в плюсах к нему нет никакой поддержки компилятора кстати). Стандартный mapcar должен иметь принимающий лямбду аргумент помеченным как совместимый с этим атрибутом. Нестандартный mapcar при попытке сделать ему прототип "совместимый с этим атрибутом" должен быть отвергнут компилятором.

Однако нестандартный mapcar, не-"совместимый с этим атрибутом", должен тоже работать, но в этом случае компилятор плюет на предложенную оптимизацию "не собирать мусор" и все-таки мусор собирает. (Компилятор дает варнинг).

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Короче: если можно, компилятор пытается отдать небоксованную лямбду, а если нельзя -- отдает боксованную (со сборкой мусора).

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от mo3r

> смысл в таком вот? Без сборки мусора невозможны функции высокого порядка, так что 3/4 возможностей лиспа теряем.

Интересуют примеры потерянных возможностей. Я их буду переписывать на плюсах специально без сборки мусора (ужасный синтаксис плюсов просьба не обсуждать -- я к нему не сильно привязан)

> Если сильно беспокоимся за память, то в лиспе есть такая вещь, как dynamic-extent (по сути, это аллокация объектов в стеке, а не куче), которую sbcl поддерживает.

А есть ли поддержка компилятора для этого???

То есть допустим мы разместили что-то на стэке, потом сделали апвард фунарг с этим -- компилятор нам скажет об ошибке? или мы сами должны догадаться?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от anonymous

> универсальное представление кода как данных

(лиспоотступничество) оно не нужно. Достаточно такого представления *только* на этапе компиляции (с возможным его read-only формой для рефлекшена, но тут тоже нужно ставить жесткие ограничения).

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от anonymous

>> Например, если написано (mapcar (lambda ...) ...), то заведомо известно, что после выхода из mapcar данная lambda больше не нужна и её можно разместить на стеке.

> Осторожно! Ты пытаешься переизобрести банальный region analisys. Лучше велосипедов не строить и сделать всё, как классики учат.

Может это так выглядит, но идея я полагаю не в этом. Все это должно работать даже если исходник mapcar недоступен и его код проанализировать нельзя -- результат анализа кода должен быть в виде атрибута одного из параметров mapcar.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от mo3r

> Упустили. ФВП основаны еще на возможности создавать новые функции по ходу работы.

> (defun make-adder (n) (lambda (x) (+ n x)))

> Основная проблема не с самой лямбдой, а тем, что она использует переменные из своего лексического окружения, и неизвестно, кто и когда их высвобождает. И получается, что без сборки мусора мы должны ограничить распределением на стеке или ручным освобождением (что практически уничтожает всю суть). Ну и кому это надо?

Не вижу тут проблемы. Я на плюсах такое повторю элементарно, и у меня все будет ОК, так как n скопируется -- да, копирование это альтернатива подсчету ссылок, и часто очень даже хорошая.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от den73

> Лично мне не так уж часто бывают нужны лямбды, которые используются нелокально. Скорее они используются именно как локальные функции.

Очень прошу привести вменяемые примеры, когда лямбды нужны нелокально (точнее, когда возникает апвард фунарг проблема).

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от anonymous

> Ты просто не используешь лямбды на полную мощь.

Хочу узреть использование на полную мощь, только пожалуста законченными примерами, а не "посмотри, там ... среди 100К строк они есть".

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

>>универсальное представление кода как данных

> (лиспоотступничество) оно не нужно. Достаточно такого представления *только* на этапе компиляции (с возможным его read-only формой для рефлекшена, но тут тоже нужно ставить жесткие ограничения).

согласен на уровне ощущений. Однако, надо как-то формализовать.

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

Синтаксиса фактически нет, есть правило: 1) корректная лисп-форма, то есть первый элемент списка -- символ 2) макрами можно поменять порядок вычислений, сохраняя структуру.

За счёт того, что список имеет однородную структуру, макра применима к любому элементу списка. Если список -- это вектор или тьюпл из разных типов, то операция "вычислить элемент списка во время компиляции = время исполнения макры" может быть не применима к этому типу.

В целом согласен, что нужен какой-то интерфейс, обрабатываемый во время компиляции, какая-то алгебра типов (в смысле, замкнутое кольцо) и обработка AST.

И синтаксис не помешает (если в конечном итоге всё равно однородно обрабатывается). Лисп не имея операций для обработки структур имеет простую "алгебру" вида car/cdr/cons , когда нужно что-то уровнем выше, вроде регэкспов и паттерн матчинга. Можно реализовать на самом лиспе, не вопрос. Но этот интерфейс имеет смысл озвучить явно.

anonymous
()
Ответ на: комментарий от anonymous

> согласен на уровне ощущений. Однако, надо как-то формализовать.

Мне эта тема интересна и я понемногу формализую. А тебе? Как тебя услышать на эту тему?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от anonymous

> Лисп не имея операций для обработки структур имеет простую "алгебру" вида car/cdr/cons , когда нужно что-то уровнем выше, вроде регэкспов и паттерн матчинга.

И вот в такой фигне (:table (:tr (:td :width 100 "abcd") (:td :width 200 "efgh"))) подозреваю что кроме car/cdr/cons нужно что-то еще... И даже скажу что вместо car/cdr/cons было бы удобнее: table.tr[1].width

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> И вот в такой фигне (:table (:tr (:td :width 100 "abcd") (:td :width 200 "efgh"))) подозреваю что кроме car/cdr/cons нужно что-то еще... И даже скажу что вместо car/cdr/cons было бы удобнее: table.tr[1].width

нужно, но это вопрос синтаксического сахара или "запихивания в платформу ради эффективности реализации" как в D/Java или однородности модели как в лиспе?

.tr[1]. -- это что, откуда этот magic number? если это 1 само выдаётся какой-то макрой как в jQuery, например, чем лучше подходы jQuery/XPath/какой-то pattern matching друг относительно друга? то есть, появляется интерфейс работы с DOM; как его теперь можно целостно расширить?
--
вот такая мысль: макры в лиспе -- это простой интерфейс откладывания вычислений на момент исполнения или компиляции программы (после исполнения макры или в момент исполнения макры).

Допустим, есть (f a b c) или сишное f(int a, float b, char c='\0')

в лиспе любую макру можно подставить вместо любой a b c (с учётом семантики f).
В Си потребуется совместимость по прототипам макры и соотв. "тьюпла"

(f a m(b) c) = f (a,((float)(*m1)(b)),c) = f(a,(T (*m2)(b,c)), T - "тьюпл типов (float,char) (+ещё значение C по умолчанию в прототипе m2) -- это если m2 возвращает несколько значений

получается, макра для конкретной функции с типизированным прототипом вместо обобщённой функции с одним прототипом с обобщённым типом-списком становится "мультиметодом" с сигнатурой, совместимой с подтьюплом тьюпла прототипа функции, в которую эта макра подставляется.

С алгеброй типов понятно: определили элементы и операции, замкнули, получили "кольцо" -- любая функция применима к любой, все различия в семантике и порядке вызова функций.

С интерфейсами хуже, получается что-то вроде хаскелевидной теории категорий. "Не все макры одинаково полезны", ага.

anonymous
()
Ответ на: комментарий от anonymous

> Лисп не имея операций для обработки структур имеет простую "алгебру" вида car/cdr/cons , когда нужно что-то уровнем выше, вроде регэкспов и паттерн матчинга

Не знаем лиспа. Читаем в стандарте defclass, defstruct, deftype, type-of, typep

На самом деле, открою страшную тайну: реально консы не годятся для хранения AST, т.к. не позволяют отслеживать исходный текст. А без этого в промышленном производстве неудобно (хотя диссеры писать вполне возможно). Если бы в Slime не было функции slime-edit-definition, никто бы им не пользовался. А он основан вовсе не на голых консах.

> вот такая мысль: макры в лиспе -- это простой интерфейс откладывания вычислений на момент исполнения или компиляции программы (после исполнения макры или в момент исполнения макры).


Это - только в идеале. На самом же деле, в Cl имеется три фазы: фаза компиляции, фаза загрузки и фаза выполнения. Соответственно, возникает вопрос частичного моделирования среды выполнения во время компиляции. Т.е., eval-when, make-load-form и т.п. Я не скажу, что в лиспе эта задача решена идеально, но ИМХО лучше, чем в других реально компилируемых языках.

Пример использования нелокальных замыканий - коллбеки. Например, массированно используются в JavaScript при отправке асинхронных запросов на сервер.

> вот в такой фигне (:table (:tr (:td :width 100 "abcd") (:td :width 200 "efgh")))

Такая фигня на самом деле является исходником для специализированного компилятор-интерпретатора. Который либо порождает строки, либо, например, порождает код, порождающий строки (для быстроты работы скомпилированного приложения).
Можно также сделать что-нибудь вроде `(:table (:tr ,(user-object) (:td "asdfasfd")))
где user-object является переменной компонентой шаблона.

> И даже скажу что вместо car/cdr/cons было бы удобнее: table.tr[1].width

Для доступа к уже созданной таблице - было бы удобнее. Для построения - нет. Поэтому, в некоторых случаях, нужно иметь оба варианта. А то и больше.

> Все это должно работать даже если исходник mapcar недоступен и его код проанализировать нельзя

Такого ограничения нет. Если даже исходник недоступен, то в стандарте описано, что такое mapcar. На базе стандарта реализуем его обработчик.

Тем не менее, я не понял смысл своего присутсвия здесь. В целом, моя задача - это разработка МП средства для разных языков, в т.ч., для С. Которое было бы практически выгодно. В денежном эквиваленте. Я пытаюсь практиковать. Раньше у меня это местами уже получалось, местами - не очень. МП, на самом деле, это вещь затратная. В ней есть ловушка чрезмерной общности, соблазн заранее везде подстелить соломку (даже там, где никогда не упадёшь). Т.е., МП - это искусство и я пытаюсь его освоить. Ослабить ограничения, сделать эту тему более выгодной. Соответственно, задачи типа "получить всё из ничего", "в начале было слово", "свести всё к минимальному набору аксиом" или "доказать, что программа всегда будет работать правильно", меня как самоцель не интересуют.

linux_org_ru, что Вы хотите в итоге от меня по этой теме?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

> В целом, моя задача - это разработка МП средства для разных языков, в т.ч., для С. <...> linux_org_ru, что Вы хотите в итоге от меня по этой теме?

Я как раз хочу видеть такой макропроцессор (МП) или хотя бы исчерпывающий список его возможностей. (А так же хочу знать, какие из них реализованы, и когда это предполагается сделать.)

Мое приблизительное видение этих возможностей:

1. (м.б. не полностью) семантическая безошибочность сгенеренного кода, т.е. чтобы компилятор не давал ошибок

2. на их основе должно получаться сделать:

А. Классы
Б. Шаблоны (полиморфизм разных видов)
В. Ленивые функции
Г. Исключения
Д. Литералы
Е. Кастомизуемый синтаксис
Ж. Любое другое расширение в том же духе, например syncronized или сборщик мусора

Так же меня интересует какой-то синтаксис, достаточно консистентый, но при этом близкий к общепринятому (тут конечно вопрос баланса...) и достаточно краткий в частоиспользуемых местах.

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

А, завтра ещё раз отправдю. den73

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.