LINUX.ORG.RU

Занимаясь веб-девелопментом


0

1

Занимаясь веб-девелопментом, я задумался: зачем столько разнородных средств? Ведь можно объединить всё в один язык. И описание, и логику - клиентскую и серверную. Языки разметки вполне годятся для программирования. Вот, например, вычимление 2 + 2 * 2:

<+>2 <*>2 2</*></+>

Ответ на: комментарий от vertexua

Простой пример, что лучше код внутри сервлета с горой println или страница с разметкой (jsp, facelets и т.д.)

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

С тем, что макросы настолько могучи?

Ну ты сам знаешь как они работают и когда их следует использовать. Тут рядом ты говорил, что используешь их в рамках одного файла. Не вижу причин не использовать их и между файлами (macros.lisp в начале системы - и всё). Каждый раз когда начинает появляться код (канонiческий, без макросов) с одинаковым рисунком - этот паттерн (рисунок, условно) можно сделать макросом. А так как наличие такого повторяющегося кода в больших проектах - типичное дело, вот макросы и появляются. Вот есть код который вводит какой-то протокол, причём есть набор протоколов код которых отличается не параметрами а алгоритмическими кусками - делают defprotocol и используют по всему проекту.

Вообще, не вижу в макросах чего-то сверх особенного чтобы ставить вопрос - «надо или не надо». Хотя, для меня «не надо» это заведомо не-ъ позиция - очень странная позиция :) Да, не серебренная пуля, но пригодится ведь - пусть будет (только без g!-макрологий).

как указывает Страуструп

Это где? Или он про си-макросы говорил?

Поскольку, значительная часть сообщества CL-разработчиков, как бы там помягче сказать, не совсем вменяема, то одна из причин широкого использования макросов как бы на поверхности. А поскольку мы не имеем много примеров впечатляющего софта на CL, то причина номер два - недостатки языка, то же как бы напрашивается.

Так что судить о силе макросов на основе подобных наблюдений как бы не очень разумно. Говорить об этом имеет смысл только на конкретных реальных примерах.

Я говорю именно о конкретных реальных примерах - CFFI, IOLib, Slime, Hemlock, Maxima, SBCL - примерно расположил в порядке возрастания количества кода и, соответственно, macrologyes в проекте. Возможно, дело в том что практика разработки на CL небольших программ и практика разработки больших - это две разных практики (так как не могу похвастаться такой серьёзной практикой - могу только предполагать).

Ты хочешь сказать, что CL гораздо более выгоден как *современный* язык в случае если в нём спрятать макросы от постороннего взора? Или вообще выкинуть. Выкинуть макросы - выкинуть SBCL, фактически макросы это «благородные костыли» SBCL, которые позволяют чего-то там компилировать без особых формализаций (там есть комментарий - CL большой, так что no way тут BNF разводить, будем делать «на макросах»).

Думаешь «осовремененный» CL без макросов позволит написать больше более качественного софта?

По крайней мере не вижу прецедента - где такой софт? Hunchentoot, возможно, там макросы играют вторичную роль. Навороты hunchentoot, которые ни раз обсуждались, тоже не принисут туда много макросни. Но сравнивать этот веб-сервер с SBCL или Maxima - как-то не получается.

З.Ы. И чего лисперы опять полезли не в тот трэдъ? :)

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

Про let - я не понимаю, ты говоришь как человек, который много времени проводит наедине с C или C++.

let люто-бешено используется в CL (Scheme, ELisp, et all.), неявно он там присутсвует и в defun/define и в lambda и много где ещё. Локальная область видимости всё-таки.

let он же where так же люто-бешено используется в любой программе на Haskell.

Т.е. не понимаю насколько, насколько ты бы меня не понимал если бы я говорил:

1) <typedef|struct|if|for...> нужны слишком редко чтобы давать их прикладным прогаммистам.

2) <typedef|struct|if|for...> вносят совершенно неожиданную вещь — внезапно программа оказывается написанной и ведёт себя хрень знает как.

3) ну допустим у тебя есть способ делать <typedef|struct|if|for...>; ты сможешь на их основе сделать хотя бы модификатор LazyIO?

не проще ли сделать интерфейс к STG?

Как-то так :)

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

> Ты хочешь сказать, что CL гораздо более выгоден как *современный*

язык в случае если в нём спрятать макросы от постороннего взора?


Нет, я просто против обожествления макросов и s-выражений, которое имеет место быть. Я за взвешенный и критический подход. А ещё за простоту.

И чего лисперы опять полезли не в тот трэдъ? :)


Одни полезли, а другие стали возражать, что не надо s-выражения куда ни поподя пихать, для лиспа же это будет лучше.

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

обожествления макросов и s-выражений

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

Одни полезли, а другие стали возражать, что не надо s-выражения куда ни поподя пихать, для лиспа же это будет лучше.

Не пихаются они сами по себе это от нас не зависит.

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

>Смотрю - что это за топик вообще? :)

Ну все, млин. Подавил интеллектом. Ажно до катаморфизмов докатился. Ну понятно, типа катаморфизм — светртка списков в алгебраические типы данных и типа в лиспе мы о-го-го могем.

Только есть одно «но». Математиков вопрос «Зачем?» не волнует. А прикладников, представь себе. Может быть где-то в идеале и можно с блекджеком и шлюхами свернуть произвольное выражение в произвольный алгебраический тип данных, но зачем?

От того что между объектами есть морфизмы, идентичными они от этого не станут. И если математику это пофиг, то программисту - нет.

И есть хоть одна практическая реализация? Типа свернули S-выражение в алгебраический тип и быстренько проверили его структуру?

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

> И кто их обожествляет?

Не пихаются они сами по себе это от нас не зависит.


Да я же не про тебя и говорю. Но если почитать ЛОР за последние лет 5 (что раньше было не знаю) на предмет лисп-срачей, то очевидно, что имеется стойкое поверье среди некоторых его посетителей, что макросы и s-выражения спасут мир от Java, индусов и вообще всех напастий.

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

Хватит уже

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

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

> let он же where так же люто-бешено используется в любой программе на Haskell.

наличие where однозначно полезно, но возможность написать my_where_with_banana_smell вредна

Т.е. не понимаю

поскольку прочел п.1 слишком буквально; а если бы еще и вспомнил, что я воспринимаю определение функции в си/с++ как let-форму, то все бы понял

ладно, п.1 читать как:

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

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

Т.е. ты не видишь необходимости в средствах которые позволяют оперировать не предметной областью (то зачем нужны ЯП - делаем типы, пишем функции, кладём по модулям/классам и т.д. - код, код, код) а самим кодом? Как #define или defmacro? Вот повторяется один и тот же рисунок в коде, но не от параметров зависит (чтобы сделать функцией) а от кусков кода - хорошо бы иметь «функцию» которая из кода составляет код. Опять же как ты предлагаешь расширять компилятор пользовательскими трансляциями/трансформациями? Или прикладник не должен этим заниматься?

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

> Вот повторяется один и тот же рисунок в коде, но не от параметров

зависит (чтобы сделать функцией) а от кусков кода


Вот серьёзно, я считаю, что это связано с недостатками языков программирования, с недостаточной выразительностью. Идеальный язык мог бы спокойно обходиться без макросов и т.п. Но такого пока нет. Но и развитие языков ведь идёт, раньше было хуже. Макросистема в стиле CL как раз хороша для экспериментального языка, что бы искать новые возможности, которые можно было бы встроить потом в реальные языки.

как ты предлагаешь расширять компилятор пользовательскими

трансляциями/трансформациями?



Боже упасти встретить такое в прикладном коде. Это ведь вообще не нужно. Ибо можно слать патчи в рассылку, или форкнуть под свои нужды.

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

И есть хоть одна практическая реализация? Типа свернули S-выражение в алгебраический тип и быстренько проверили его структуру?

Ты продолжаешь говорить об s-выражениях как о сущностных элементах в лиспах, тогда как это просто форма. Форма записи программы, тоже что и отступы в Python или Haskell. Единственное отличие - можно работать с этой формой, т.е. можно работать с тем кодом, который мы видим на экране, безотносительно предметной области. Это синтаксис, а сущность и семантика - типы, АТД, объекты-стрелки, - стоит за формой.

Вот типичный пример:

data Tree a = Leaf a | Branch (Tree a) (Tree a)

type TreeAlgebra a r = (a -> r, r -> r -> r)

cata :: TreeAlgebra a r -> Tree a -> r
cata (f, g) (Leaf x)     = f x
cata (f, g) (Branch l r) = g (cata (f, g) l) (cata (f, g) r)

treeSum :: (Num a) => Tree a -> a
treeSum = cata (\x -> x, \l r -> l + r)

Настоящий АТД, и катаморфизм для него. Мы же не будем смотреть на синтаксис - как это записано, тут главное сущности, которые мы ввели.

Но для CL тоже самое - форма, s-выражения, в данном случае, не играют роли, а сущности за записью стоят те же:

(defdata (tree a)
  (leaf a)
  (branch
    (left  (tree a))
    (right (tree a))))

(defun cata (f g tree)
  (typecase tree
    (leaf   (funcall f (leaf tree)))
    (branch (funcall g (cata f g (left  tree))
                       (cata f g (right tree))))))

(defun tree-sum (tree)
  (cata #'identity #'+ tree))

Надо заметить - это по-прежнему CL, по-прежнему динамический, также нет pattern-matching и point-free. Но я ещё хочу сказать, что можно ввести то чего нет - и это будет по-прежнему CL. А вот введённые АДТ и катаморфизм такие же «железные» как и в Haskell.

Теперь немного дальше - нужно определить

data Tree2 a = Leaf a | Branch (Tree a) (Tree a)
data Tree3 a = Leaf a | Branch (Tree a) (Tree a) (Tree a)
data Tree4 a = Leaf a | Branch (Tree a) (Tree a) (Tree a) (Tree a)
data Tree5 a = Leaf a | Branch (Tree a) (Tree a) (Tree a) (Tree a) (Tree a)
data Tree6 a = Leaf a | Branch (Tree a) (Tree a) (Tree a) (Tree a) (Tree a) (Tree a)
data Tree7 a = Leaf a | Branch (Tree a) (Tree a) (Tree a) (Tree a) (Tree a) (Tree a) (Tree a)

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

(macrolet ((deftree (n)
             `(defdata (tree a) (leaf a) (branch ,@(each (s (gensym-list n)) `(,s (tree a)))))))
  (many deftree 2 3 4 5 6 7))

но сами АДТ (сущности) тут ни при чём - s-выражения/макросы это про форму.

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

Вот повторяется один и тот же рисунок в коде, но не от параметров зависит (чтобы сделать функцией) а от кусков кода - хорошо бы иметь «функцию» которая из кода составляет код

эти куски кода представляют собой что-то, значит имеют какой-то тип данных; соответственно для параметризации достаточно функции, принимающией этот тип данных, или шаблона (шаблоны, в общем-то, те же функции)

кроме того, такой подход позволяет параметризовать то, что макросы *не могут* параметризовать

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

inline void my_sleep(uint time, enum TimeUnit time_unit) { sleep(time*time_unit); }

при этом онa позволяет написать my_sleep(1, very_slow?DAY:HOUR), в отличие от макроса

понятно, что не хватает синтаксического сахара — тогда берем с++0х и вводим литералы 42_hour, 37_min, 40_ms

в общем, ридеровские макры, система типов и partual evaluation делают обычные макры ненужными <--- вот это критиковать :-)

впрочем, плюсовый подход как всегда недостаточно развит в с++, его нужно двинуть дальше; конкретно, плюсовый шаблон (для литералов из с++0х потребуется шаблон) не может легко быть одновременно и функцией

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от quasimoto
-             `(defdata (tree a) (leaf a) (branch ,@(each (s (gensym-list n)) `(,s (tree a)))))))
+             `(defdata (,(symbolicate 'tree- n) a) (leaf a) (branch ,@(each (s (gensym-list n)) `(,s (tree a)))))))
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

data Tree7 a = Leaf a | Branch (Tree a) (Tree a) (Tree a) (Tree a) (Tree a) (Tree a) (Tree a)

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

template<int i>struct Tree { virtual ~Tree(); };
template<int i>struct Leaf: public Tree<i> { };
template<int i>struct Branch: public Tree<i> { Tree<i> value[i]; };
www_linux_org_ru ★★★★★
()
Ответ на: комментарий от quasimoto

> `(defdata (,(symbolicate 'tree- n) a) (leaf a) (branch ,@(each (s (gensym-list n)) `(,s (tree a)))))))

ахренеть; по-моему плюсовые шаблоны понагляднее и там вероятность ошибки поменьше

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

шаблоны, в общем-то, те же функции

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

кроме того, такой подход позволяет параметризовать то, что макросы *не могут* параметризовать

Шаблоны это подмножество CL-макросов, а не наоборот (ясчитаю :)).

LoL-ский макрос, позволяющий задавать сон в единицах измерения

Очевидно, что такой лоловский макрос - глупость. sleep всегда был обычной функцией. Вот пример, когда тащат макросы куда не нужно.

в общем, ридеровские макры, система типов и partual evaluation делают обычные макры ненужными <--- вот это критиковать :-)

Ну так где тот язык (даже в виде proof of concept) в котором эти три заменяют CL-like-макросы? Неужно в с++0х - тогда show me the code, в а в Haskell оно очень непрозрачно.

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

Не надоело ветряные мельницы строить? А то ночь уже :)

здесь видимо нужны dependent types, а без них получается обычный

Это обычный параметрический тип.

хотя и типизрованный (т.е. лучше лисповского)

Опять старый миф «нет типов, потому что ЛИСП», ага.

ахренеть; по-моему плюсовые шаблоны понагляднее и там вероятность ошибки поменьше

Так и знал, что кто-нибудь это скажет - «ага, патчик, лисповый код, вероятность ошибки over 90%» или «такое количество повторяющихся слов в ЛИСПе - очевидная ошибка в архетектуре».

А вообще - дело привычки. Мне вот C++ шаблоны гораздо сложнее понимать чем CL макросы.

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

> Очевидно, что такой лоловский макрос - глупость. sleep всегда был обычной функцией. Вот пример, когда тащат макросы куда не нужно.

ВСЕ макросы ничем не лучше этого :-)

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

ВСЕ макросы ничем не лучше этого :-)

Тогда man http://www.linux.org.ru/jump-message.jsp?msgid=5513477&cid=5517719

В предпоследнем листинге вводятся шесть типов на Haskell, в последнем - те же шесть но с помощью макрогенерации по рисунку. Покажи на *любом* языке такой способ генерации рабочего кода.

Поясню - рисуночный код может быть размером 100 строк, и возникать этот рисунок может 100 раз - что лучше 100*100 строк кода или 100+100+1 строк?

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

> А вообще - дело привычки. Мне вот C++ шаблоны гораздо сложнее понимать чем CL макросы.

Есть объективная оценка.

Берем не-макро-код, считаем, сколько токенов надо вставить и удалить, чтобы получить макро-код.

В плюсовом случае надо тупо расставить template<int i> и <i>

В случае лиспа надо ЕЩЕ знать про новые функции, gensym-list например.

___________________________________

А что делать, если тебе надо в лиспе tree<i>, где i вводится с клавиатуры? Макрос обломается, придется писать еще и функцию. Нынешний с++, правда, не лучше (хотя его можно и подтянуть).

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

В случае лиспа надо ЕЩЕ знать про новые функции, gensym-list например.

Поэтому я и говорю что макросы - более сложны чем шаблоны, по мощности. Можно не то что gensym-list, но и произвольным образом обрабатывать отданный макросу код.

А что делать, если тебе надо в лиспе tree<i>, где i вводится с клавиатуры? Макрос обломается, придется писать еще и функцию.

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

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

В предпоследнем листинге вводятся шесть типов на Haskell, в последнем - те же шесть но с помощью макрогенерации по рисунку. Покажи на *любом* языке такой способ генерации рабочего кода.

Tree7 и компания что ли? Уже показал на плюсах

хотя там все же ошибка — забыл вытащить тип «а» в шаблон

template<class A, int i>struct Tree { virtual ~Tree(); };
template<class A, int i>struct Leaf: public Tree<A,i> { A value; };
template<class A, int i>struct Branch: public Tree<A,i> { Tree<A,i>* value[i]; };

и параметрический полиморфизм так просто не получить.

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

Эта штука вводит 6 разных классов (или шаблон, или что там)?

И что, можно таким образом произвольный код рисовать? Я знаю, что нет :)

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

> Эта штука вводит 6 разных классов (или шаблон, или что там)?

столько, сколько потребуется (делает она это лениво — только когда ты объявишь переменную нужного класса)

И что, можно таким образом произвольный код рисовать? Я знаю, что нет :)

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

но я и не предлагал это как идеал — макросы (кроме ридеровских) не нужны :-)

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

> Ну что ж, буду иметь это ввиду :)

это надо не иметь в виду, а критиковать :-)

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

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

Это ж нужно про C++ думать - лениво. Мне что-то ясно относительно CL, тебе - относительно C++ и мы пытаемся со своих точек зрения думать про «вообще».

У меня с С++ не очень. Вот на си простейший пример (навеяно memory-accessors из CFFI):

/*
 * Skit #1
 */

typedef struct {
  ...
} Object;

#define TYPE(O) ...

static inline fn_1 (Object *object_1) { ... }
static inline fn_2 (Object *object_1, Object *object_2) { ... }
static inline fn_3 (Object *object_1, Object *object_2, Object *object_3) { ... }
...
static inline fn_n (Object *object_1, Object *object_2, Object *object_3, ..., Object *object_n) { ... }

SomeType
foo (Object *object_1, Object *object_2, Object *object_3, ..., Object *object_n) {
  SomeType result;
  switch (TYPE(object)) {
    case TYPE_1 : result = fn_1(object_1); break;
    case TYPE_2 : result = fn_2(object_1, object_2); break;
    case TYPE_3 : result = fn_3(object_1, object_2, object_3); break;
    ...
    case TYPE_N : result = fn_n(object_1, object_2, object_3, ..., object_n); break;
  }
  return result
}

Нужно построить макрос-генератор такого - в зависимости от n, и возможно от (...)-точий:

macro(3, ...тут-кусок-кода...или-не-нужно?...в-продакшене-не-поймут-да?...);
macro(5, ...);
macro(7, ...);

Если получится - можно усложнить. Если задействовать сторонний препроцессор - можно потребовать доступа к информации о типах.

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

хрен с системой типов (я не понял твой код и откуда взялось macro)

лучше разберемся — какой из лисповских макросов неудобно или невозможно представить в виде partial evaluation какого-то кода? предполагаем, что partial-eval действует разумно, не не гениально

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

> это херотень какаято, не s-выражения

Именно. Вывод?

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

> (mama has let, papa has let, so you has it too, or you not a human?)

Не, я негров люблю.

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

> А представь систему, которая покрывает ВСЁ. Какая она должна быть громоздкая, на скольки громадных консорциумах ее будут стандартизировать и какому количеству человек и организаций она должна угодить? С такой неуклюжестью она будет отставать лет на 10 от рынка. А как быть с упоротыми? Одни до смерти будут стоять за одно, а другие - за противоположное.

Победа как раз в модульности, переносимости, интеграции с друг другом и взаимозаменяемости компонентов. man java

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

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

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

>Надо заметить - это по-прежнему CL, по-прежнему динамический, также нет pattern-matching и point-free. Но я ещё хочу сказать, что можно ввести то чего нет - и это будет по-прежнему CL. А вот введённые АДТ и катаморфизм такие же «железные» как и в Haskell.

Дык, вот в этом и проблема. Хотя, АТД можно реализовать и на ООП-подсистеме: взять ту же Скалу. Она кстати, такое позволяет творить с ООП, что невольно начинаешь сомневаться в адекватности тех кто был до. И самое главное — формально, в отличие от C++, Java и т.п.

Кроме того, к каждому символу лиспа можно привесить кучу метаданных

Другая проблема, для каждого нашего списка, генерируемого S-выражением, придется определять порождающие алгебры. Ведь у нас будут задачи покруче, чем примитивная свертка. Задачи предметной области никто не отменял. Тоже задачка так-себе, ведь никто не привык смотреть на предметную область с такой позиции. Еще нужен аппарат, который проверял бы наши алгебры на непротиворечивость.

Короче, вопросов пока больше чем ответов.

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

>Победа как раз в модульности, переносимости, интеграции с друг другом и взаимозаменяемости компонентов. man java

man scala

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

Почему мы продолжаем говорить об s-выражениях? Попробую подойти с другой стороны. В посте на который ты отвечаешь я говорил о том что s-выражения это только форма, но ты видимо не так понял - не в том смысле, что это форма с помощью которой что-то там моделируется. Скажем прямо - можно выкинуть всякие s-выражения и макросы, всё, забыли - нет их. Они не имеют отношения к предмету разговора, и только сбивают с толку. Т.е. такой мысленный эксперимент - допустим на момент создания МакКарти лиспа s-выражения были в чём-то исключительны, но теперь, если взять современный Сommon Lisp и выкинуть s-выражения и макросы - много останется? Так вот - останется почти всё что есть, мало что пропадёт. (До этого я ещё заметил, что можно писать определения тегированых типами деревьев в виде s-выражений, но видимо зря говорил - можно «не впихивать АТД в простенькие списки», а использовать настоящие АТД - на самом деле тут есть выбор).

Тот код можно написать без скобок:

defdata (tree a)
  leaf a
  branch
    left  (tree a)
    right (tree a)

defun cata (f g tree)
  typecase tree
    leaf   (funcall f (leaf tree))
    branch (funcall g (cata f g (left  tree))
                      (cata f g (right tree)))

defun tree-sum (tree)
  cata #'identity #'+ tree

можно даже сделать такой ридер - от этого ничего не изменится. Т.е. s-выражения тут вообще ни причём. Почему из наличия в CL одной выразительной возможности (s-выражения) ты обесцениваешь автоматически все остальные? Почему «если списки, то не может быть типов»? На основе какой литературы ты сделал вывод, что типы в CL имеют отношения к s-выражениям? Что нет возможности делать АТД? Композитные, вариантные типы, классы типов как first-class объекты несводимые к каким-то там s-выражениям? Где ты прочёл что «всё списки и символы» и прочую муру из 50-70-ых годов прошлого века? :)

Давай сначала:

data Tree a = Leaf a | Branch (Tree a) (Tree a)

эквивалентно

(defdata (tree a)
  (leaf a)
  (branch
    (left  (tree a))
    (right (tree a))))

нет тут списков/s-выражений и т.д. Этот код определяет *настоящие* типы branch, leaf и tree

(type-of (branch (leaf 1) (leaf 2)))
;=> branch

Этот тип branch не является типом list и никакого отношения к спискам вообще не имеет.

(type-of (leaf 1))
;=> leaf

Также - leaf это не список это *настоящий*, введённый, тип.

Haskell    CL

Leaf       leaf
Branch     branch
Tree       tree
:t         type-of

Из стандарта - defstruct

(defstruct foo
  (a nil :type bar)
  (b nil :type fooz))

тоже что и

data Foo = Foo { a :: Bar, b :: fooz }

только defstrcut может агрегировать по другим структурам и имеет десятки разных опций определяющих его поведение.

deftype похож на классы типов (только классы - обычные типы, т.е. могут быть вариантные типы вариантных типов и т.д.)

(deftype one-of-type () '(or type1 type2 type3))

тип one-of-type - вариантный от типов type1 type2 type3.

или

(deftype foo () '(member :a :b :c))

то же что

data Foo = A | B | C

сам тип может зависеть от произвольного количества параметров (объектов) а спецификация типа может быть сгенерирована:

(flet ((equidimensional (a)
         (or (< (array-rank a) 2)
             (apply #'= (array-dimensions a)))))
  (deftype square-matrix (&optional type size)
    `(and (array ,type (,size ,size))
          (satisfies equidimensional))))

т.е. некое подобие параметрических/зависимых типов.

А тот defdata - простейший макрос, от своих определений он генерирует ряд defstruct и один deftype. И кстати, всё это также не имеет отношения к CLOS (можно использовать методы для этих объектов - но сами типы/объекты не являются CLOS-специфичными).

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

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

А мужики (Google, IBM, Oracle) то и не знали... )) Да кто они такие все по сравнению с gestapochki

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

> >А ставить рядом Java, которая отстаёт от технологий (не знаю, как насчёт рынка) лет на 50, - вообще неуместно.

А мужики (Google, IBM, Oracle) то и не знали... )) Да кто они такие все по сравнению с gestapochki

Это не мужики, а корпорации. Это неразумные существа, они ничего не могут знать.

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

при таком подходе тебе видимо надо изучить Qi, потом нам расскажешь

При каком? При чём тут Qi? Не собирался и не собираюсь его изучать, спасибо :)

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

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

1) Пользователь определяет АТД,

2) Функция-катаморфизм для этого АТД должна определиться автоматически.

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

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

Как выглядит?

В первом приближении как-то так:

(defmacro map-each ((element list) &body body)
  `(mapcar #'(lambda (,element) ,@body) ,list))

;;;; implementation of the haskell-like `data' special form

;;; We assume that ADT is a general topological isomorphism in the following form:
;;;
;;;   NewType ~ TypeExpr(NewType)
;;;
;;; Where NewType is
;;;
;;;   NewType ::= TypeName | TypeName [TypeParameter]*
;;;   TypeName, TypeParameter is symbolics
;;;
;;; And TypeExpr is
;;;
;;;   TypeExpr      ::= StructureType <|> StructureType <|> ... <|> StructureType
;;;   StructureType ::= AtomicType | CompositeType
;;;   AtomicType    ::= Constructor
;;;   CompositeType ::= Constructor [Type | Accessor :: Type]*
;;;   Type          ::= NewType (for RDT) | SomeTypeInUniverse
;;;   Constructor, Accessor is symbolics
;;;   SomeTypeInUniverse is valid type expression in host language
;;;
;;; It means that:
;;;
;;;   <|>           ~ topological union
;;;   `Constructor' ~ n-ary topoligical operation
;;;

;;; CL specific notes:
;;;
;;;   Atomic types maps into `defstruct' and `defparameter'
;;;   Composite types maps into `defstruct'
;;;   Variant types maps into `deftype/or'
;;;

(defmacro defdata (new-type &rest type-expression)
  (multiple-value-bind (type-name type-parameters)
      (etypecase new-type
        (symbol (values new-type nil))
        (cons   (values (first new-type) (rest new-type))))
    (let ((constructors (map-each (structure-type type-expression)
                          (etypecase structure-type
                            (symbol structure-type)
                            (cons   (first structure-type))))))
      `(progn
         (deftype ,type-name () '(or ,@constructors))
         ,@(map-each (structure-type type-expression)
             (etypecase structure-type
               ;; atomic type
               (symbol `(progn
                          (defstruct (,structure-type (:constructor ,structure-type ())))
                          (defparameter ,structure-type (,structure-type))))
               ;; composite type
               (cons   (let* ((constructor (first structure-type))
                              (slots       (rest  structure-type))
                              (accessors   (if (and (= 1 (length slots))
                                                    (symbolp (first slots)))
                                               (list constructor)
                                               (mapcar #'first slots))))
                         `(defstruct (,constructor (:constructor ,constructor ,accessors))
                            ,@(if (and (= 1 (length slots))
                                       (symbolp (first slots)))
                                  `((,constructor nil :type ,(first slots)))
                                  (map-each (slot slots)
                                    `(,(first slot) ,(third slot) :type ,(second slot)))))))))))))

Отображается в plain CL это так:

;;; Натуральные числа

                ;; Nat ~ Zero + Nat*
(macroexpand-1 '(defdata nat zero (succ nat)))
;=>
(PROGN
 (DEFTYPE NAT () '(OR ZERO SUCC))
 (PROGN (DEFSTRUCT (ZERO (:CONSTRUCTOR ZERO NIL))) (DEFPARAMETER ZERO (ZERO)))
 (DEFSTRUCT (SUCC (:CONSTRUCTOR SUCC (SUCC))) (SUCC NIL :TYPE NAT)))

(succ (succ zero))
;=>
#S(SUCC :SUCC #S(SUCC :SUCC #S(ZERO)))

;;; Гетерогенное дерево

                ;; Tree ~ Any + Tree * Tree
(macroexpand-1 '(defdata tree (leaf t) (branch (left tree) (right tree))))
;=>
(PROGN
 (DEFTYPE TREE () '(OR LEAF BRANCH))
 (DEFSTRUCT (LEAF (:CONSTRUCTOR LEAF (LEAF))) (LEAF NIL :TYPE T))
 (DEFSTRUCT (BRANCH (:CONSTRUCTOR BRANCH (LEFT RIGHT)))
   (LEFT NIL :TYPE TREE)
   (RIGHT NIL :TYPE TREE)))

(branch (leaf "string") (leaf 1000))
;=>
#S(BRANCH :LEFT #S(LEAF :LEAF "string") :RIGHT #S(LEAF :LEAF 1000))

Аналогом instance Show, Instance Eq и т.д. будет написание методов для этих типов, например:

(defmethod print-object ((leaf leaf) stream)
  (format stream "~A" (leaf-leaf leaf)))

(defmethod print-object ((branch branch) stream)
  (format stream "<~A . ~A>" (branch-left branch) (branch-right branch)))

(branch (leaf "string") (leaf 1000))
;=>
<string . 1000>

Вот и получается - пока мы «внутри» языка, никаких s-expression и макросов нет, есть настоящии типы, классы, методы, динамические и статические привязки, ну и так далее; но если мы хотим выйти «наружу» ,на метауровень, и говорить в терминах расширений CL, то тогда начинаются эти s-выражения и макросы. Например, чтобы сделать defdata - их пришлось привлечь, но «внутри» когда мы начинаем делать эти data - вводятся обычные примитивы (типы, объекты, классы и функции, никаких «списков и символов»).

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

я не понял твой код и откуда взялось macro

Имеется ввиду, что

macro(1);

/* даст */

typedef struct { ... } Object;
#define TYPE(O) ...

static inline fn_1 (Object *object_1) { ... }

SomeType
foo (Object *object_1) {
  SomeType result;
  switch (TYPE(object)) {
    case TYPE_1 : result = fn_1(object_1); break;
  }
  return result
}
macro(3);

/* даст */

typedef struct { ... } Object;
#define TYPE(O) ...

static inline fn_1 (Object *object_1) { ... }
static inline fn_2 (Object *object_1, Object *object_2) { ... }
static inline fn_3 (Object *object_1, Object *object_2, Object *object_3) { ... }

SomeType
foo (Object *object_1, Object *object_2, Object *object_3) {
  SomeType result;
  switch (TYPE(object)) {
    case TYPE_1 : result = fn_1(object_1); break;
    case TYPE_2 : result = fn_2(object_1, object_2); break;
    case TYPE_3 : result = fn_3(object_1, object_2, object_3); break;
  }
  return result
}

Но это плохой пример, давай лучше (АТД -> генерирует функцию-катаморфизм избавленную от страшных рекурсий) покажи :)

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

лучше разберемся — какой из лисповских макросов неудобно или невозможно представить в виде partial evaluation какого-то кода? предполагаем, что partial-eval действует разумно, не не гениально

Нужно отвлечься от CL или C++ и (чего уж там) рассматривать любые языки вообще :)

1) Полагаем, что нам нужны только контекстно-свободные языки. Их можно разбирать грамматиками, задающимися продукциями Хомского.

2) Есть дерево вывода, т.е. AST (синтаксическое и дерево).

3) Его можно представлять в виде АТД:

-- |
-- Для чистого лямбда-исчисления
--
data LCTerm = Symbol | Lambda [Symbol] LCterm | Application LCTerm [LCTerm]

-- Lambda [Symbol] LCterm и Application LCTerm [LCTerm] это сахар, т.е. предполагается каррирование

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


type Symbol = String

data InfixOp = Plus | Minus | Prod | Div -- etc.

-- |
-- define simple AST (Term there)
--        -- ** - primitive types
data Term = -- * - atomic terms
            Token  Symbol
          | String String
          | Bool   Bool
          | Number Integer
            -- * - list/array terms
          | List       [Term]
          | DottedList [Term] Term
            -- etc.
          -- ** - special forms
          | BinaryOp   Term InfixOp Term
          | IfThenElse Term Term    Term
            -- etc.
          -- ** - functional terms
            -- * - foreign functional terms
          | PrimitiveFunction ([Term] -> ThrowsError   Term)
          | IOFunction        ([Term] -> IOThrowsError Term)
            -- * - abstract functional terms
          | Function { variables :: [Symbol], body :: [Term], closure :: Env}
          | Application { functional :: Term, arguments :: [Term]

4) У такого дерева есть функция свёртки с типом

:: ряд функций для каждого конструктора -> Term -> Term

тип функций для конструкторов:

:: ряд типов параметров -> тип конструктора

5) Какие функции возникают над таким АТД:

чтения

parse :: String -> AST
read  :: IO     -> AST

печати

show  :: AST -> String
print :: AST -> IO

Далее есть счётное множество функций с типами AST -> AST, главная:

Eval :: AST -> AST

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

Далее, из Eval путём каких-то преобразований может быть построин счётный класс функций {PartialEval} :: AST -> AST. Какого исключение класса {PartialEval} из всего множества функций с типом AST -> AST? Мне почему-то очевидно, что непустое. Вот все остальные такие функции - макросы. Функции {PartialEval} являются макросами тоже? Мне почему-то опять очевидно, что да.

Технически это «да» тоже должно быть очевидно:

Term* read(Stream stream) {
  -<- хук на таблицу в которой одна за другой лежат пользовательские
  -<- процедуры чтения/разбора.
  продолжение - стандартный читатель/парсер
}
+ процедура, которая добавляет в таблицу пользовательскую функцию чтения

= макросы чтения.

Term* eval(Term* Term) {
  -<- хук на таблицу в которой одна за другой лежат пользовательские
  -<- процедуры трансляции кода (:: Term -> Term).
  продолжение - стандартный вычислитель, или интерфейс к компилятору.
}
+ процедура, которая добавляет в таблицу пользовательскую функцию трансляции кода

= макросы.

И последнее, весьма важное, замечание. Все эти рассуждения это рассуждения в рамках host языка (или мета-языка) на котором мы делаем модельный язык. Функции target языка, это обычные элементы того АТД - PrimitiveFunction, IOFunction и Function, а вот счётное множество AST -> AST это предметы мета-языка. Что значит наличие в target языке средств PartialEval - это доступ из target языка к классу функций {PartialEval} :: AST -> AST из метаязыка. А наличие в target языке макрсов - доступ вообще к любым функциям типа AST -> AST из мета-языка.

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

А наличие в target языке макрсов - доступ вообще к любым функциям типа AST -> AST из мета-языка.

Соответсвенно, АТД того языка должно быть дополнено

data Term = 
          ...
            -- * - abstract functional terms
          | Function { variables :: [Symbol], body :: [Term], closure :: Env}
          | Application { functional :: Term, arguments :: [Term]
            -- * - meta terms
          | Macro { templates :: [Term], translator :: [Term], macro-closure :: MacroEnv }
          | Reader { firstString :: String, reader :: String -> Term }

И это уже требует от реализации языка с таким АТД, задающим AST (+ семантические атрибуты, которые просматриваются), такой реализации eval (на host языке!), который бы зависел от определений Macro из MacroEnv из target языка, т.е. поведение eval из реализации зависит от того что именно написано в коде целевого языка (это мета-eval). Это и есть макросы в духе CL. Аналогично реализация read из host языка зависит от определений Reader в target языке, и поведение read из реализации зависит от того что написано в коде целевого языка (уф, это мета-read :)). Но в предыдущем посте это тоже есть (техническое «да» где).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto
-         | Macro { templates :: [Term], translator :: [Term], macro-closure :: MacroEnv }
+         | Macro { templates :: [Term], translator :: [Term], macroClosure :: MacroEnv }
quasimoto ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.