(Продолжение) Критика ООП подхода

ramsit

Ладно, мужики, харе сраться, давайте лучше позитив обсуждать
Вот порция отличного кода с подробными объяснениями, реализация текстовой версии пасьяс-советника, менее 300 строчек кода на все про все, без внешних библиотек.
DSL на основе кодогенерации, и никакого ООП.
http://swizard.info/articles/solitaire/article.html
Если кто-то из тех, кто говорит, что без ООП нельзя ступить ни шагу, сделает то же самое на своем любимом языке за меньшее количество кода (без читерства типа целевых библиотек то я соглашусь :)

luna89

Если кто-то из тех, кто говорит, что без ООП нельзя ступить ни шагу, сделает то же самое на своем любимом языке за меньшее количество кода (без читерства типа целевых библиотек то я соглашусь
Можно написать няшный код на языке с компрехеншенами, например на хаскеле или C#. Такой код:

(slt/defrule pile s stock) (pile w waste) (top-card c (on s) unknown
(reveal-top s)
(move-top s w

Преобразуется в такой:

do
s <- pile stock
w <- pile waste
c <- top_card $ on s
reveal_top s
move_top s w

Отладка будет попроще, и семантика будет понятна из знания языка.
Хотя мб там что-то есть что не вписывается в этот подход, я поверхностно просмотрел.

ramsit

Там самая соль даже не в том, как выглядит конечный dsl, синтаксис можно наваять абсолютно любой, самая соль в трансляторе из этого dsl в родной язык
Это был пример DSL вместо ООП, кажется говорил выше, что без ООП никак

kokoc88

Ладно, мужики, харе сраться, давайте лучше позитив обсуждать
Позитив мы обсуждаем в другом тредике.
реализация текстовой версии пасьяс-советника
Он просто отображает возможные ходы за 300 строк.
Если кто-то из тех, кто говорит, что без ООП нельзя ступить ни шагу, сделает то же самое на своем любимом языке за меньшее количество кода (без читерства типа целевых библиотек то я соглашусь :)
Ни шагу можно, есть задачи, которые хорошо ложатся на ФП.
Можно запросто нагуглить полные солверы для пасьянса.
Вот полный солвер на Java.
Вот реализация солвера в лоб на С++.
В обоих случаях можно выдрать вычисление возможных ходов. Судя по объёму кода, получатся те же 300 строк, если не меньше.
Для сравнения объёмов кода полный солвер на Common Lisp.

luna89

Он просто отображает возможные ходы за 300 строк.
Думаю, целью автора было продемонстрировать, как написать декларативный код с помощью макросов. На таком маленьком примере преимущест особо не видно, потому что получается в сущности самодельный синтаксис для list comprehensions.
Я думаю подход мог бы зарулить, например, на создании движка для регэкспов или генератора парсеров.

ramsit

Я думаю подход мог бы зарулить, например, на создании движка для регэкспов
таки perl-like регэкспы на макросах cl оказываются быстрее оригинальных перловых (в несколько раз, а иногда и десятков раз) за счет кодогенерации на лету :)

ramsit

Ни шагу можно, есть задачи, которые хорошо ложатся на ФП.
Это не ФП. CL - чисто императивный язык, и даже макросы в нем "грязные"
Вот полный солвер на Java.
Вот реализация солвера в лоб на С++.
В обоих случаях можно выдрать вычисление возможных ходов. Судя по объёму кода, получатся те же 300 строк, если не меньше.
Ну справедливости ради там чуть ли не вдвое больше кода, если смотреть собственно без думалки.
Во-вторых, код по моей ссылке таки содержит саму думалку на 70 строк, а без них размер солвера - 200 с хвостиком строк.
Так что разрыв вполне себе колоссальный (код на жабе ~500 чистых строк движок + столько же думалки).
Вообще, комрад swizard пишет исключительно хороший код
Для сравнения объёмов кода полный солвер на Common Lisp.
Там нет DSL

kokoc88

Ну справедливости ради там чуть ли не вдвое больше кода, если смотреть собственно без думалки.
Да, код полного солвера в два раза больше, а в твоём примере просто перечисление разрешённых ходов.
Во-вторых, код по моей ссылке таки содержит саму думалку на 70 строк, а без них размер солвера - 200 с хвостиком строк.

Он предлагает тебе руками выбрать ход. Код на Java делает много других действий.
Так что разрыв вполне себе колоссальный (код на жабе ~500 чистых строк движок + столько же думалки).
Оттуда нужно просто выкинуть всякие отрисовки, временные данные, комментарии. Так что размер кода сравнимый, никакого колоссального отрыва я не вижу.
Там нет DSL

Ну и что, зато там полный солвер.

Ivan8209

> генератора парсеров
У меня есть стойкое ощущение, что в этой области ФП уже просто
всех победило даже в ОО языках. Есть какие-то новые работы,
которые обсуждают подходы, отличающиеся от комбинаторов?
---
Q51: Hарод, а вы стабильным софтом пользоваться не пробовали?
A51: Пробовали, но мэйнфреймы с дизель-генераторами не везде есть.

6yrop


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

Я не знаю пасьянса. :( Всё же придется долго и муторно вникать ....
А так, с первого взгляда, вроде похоже на то о чем я — решая задачу, пишем код, кое где не особо задумываясь, переписываем условие на языке программироавния

Dasar

сли кто-то из тех, кто говорит, что без ООП нельзя ступить ни шагу
Текущее взвешенное мнение (к которому я присоединяюсь что ФП хороша для описания преобразования данных из одного вида в другой, а ООП хороша для описания многообразного меняющегося мира.
В большинстве приложений есть и то, и другое - и, соответственно, удобно, когда обе парадигмы можно использовать вместе.
ps
Смотрел сейчас мельком Roslyn (компилятор C#-а на C#-е) - в нем активно используются обе парадигмы.

Ivan8209

> ФП хороша для описания преобразования данных из одного вида в другой,
> а ООП хороша для описания многообразного меняющегося мира.
А когда оопешники пытаются симулировать FRP, это как называется?
---
Моё знакомство с ООП началось со слова "интифада."

Dasar

А когда оопешники пытаются симулировать FRP, это как называется?
Они используют парадигму реактивного программирования, а не симулируют FRP. Реактивное программирование ортогонально ФП и ООП.

Hastya

Гхм. А почему DSL противопоставляется ООП? Есть и DSL с объектами.
Или весь срач пришел в итоге к мерянью строчками кода?

ramsit

Гхм. А почему DSL противопоставляется ООП? Есть и DSL с объектами.
Да нипочему в общем-то. Зашла речь, можно ли эффективно обойтись без ООП, вот и перебираем варианты. Но и это тоже сферическая фигня в вакууме, в отрыве от конкретной задачи. Так что по сути трепем языком
Или весь срач пришел в итоге к мерянью строчками кода?
Да строчки кода это фигня. конечно, и я готов признать, что с этими строчками слил. Я просто хотел привести пример красивого кода.

6yrop

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

Меня тут обвинили в непризнании авторитетов. Какого авторитета признавать или ?

Dasar

Это был пример DSL вместо ООП, кажется говорил выше, что без ООП никак
Я утверждал, что DSL слабо помогает реализовать полиморфизм. И в этом коде я не вижу особого использования полиморфизма.

ramsit

Я утверждал, что DSL слабо помогает реализовать полиморфизм. И в этом коде я не вижу особого использования полиморфизма.
Вся соль примера, что с этим подходом он может оказаться просто не нужен
Зачем тебе феррари, когда у тебя есть самолет :)

Dasar

Вся соль примера, что с этим подходом он может оказаться просто не нужен
Никакая парадигма не может уменьшить внешнюю сложность задачи. Если есть полсотни видов widget-ов с различным поведением, или полсотни разных видов коллекций с различным поведением, или полсотнки видов ФС с различным поведением, то никакой DSL это не уменьшит.
Если есть полсотни объектов с разным поведением и полсотни операций над ними, то без полиморфизма придется закодировать 2500 различных вариантов поведения, что никакой DSL не облегчит.

Ivan8209

>> А когда оопешники пытаются симулировать FRP, это как называется?
> Они используют парадигму реактивного программирования, а не симулируют FRP.
> Реактивное программирование ортогонально ФП и ООП.
Всё бы хорошо, но они сами пишут, что они пытаются сделать FRP,
но не на хаскеле, а на яве или питоне. Вон, например, некто
"John Peterson" со товарищи так и пишут: "We present our
experience integrating Functional Reactive Programming (FRP)
into a new host language, Python..." Или некто "Antony Courtney:"
"This paper presents Frappe, an implementation of FRP in the
Java programming language." Так что ты, дебилушка, опять нагнал.
---
"Vyroba umelych lidi, slecno, je tovarni tajemstvi."

ramsit

Никакая парадигма не может уменьшить внешнюю сложность задачи. Если есть полсотни видов widget-ов с различным поведением, или полсотни разных видов коллекций с различным поведением, или полсотнки видов ФС с различным поведением, то никакой DSL это не уменьшит.
Если есть полсотни объектов с разным поведением и полсотни операций над ними, то без полиморфизма придется закодировать 2500 различных вариантов поведения, что никакой DSL не облегчит.
Надо смотреть на конкретную задачу. Тут все равно придется описать процедуры доступа к каждой ФС, и никакая DSL и кодогенерация чудесно не сообразит, как надо обращаться к dropbox, а как к локальной фс. При этом, каким образом они будут вызываться - дело десятое, на сложность это не повлияет.
Есть куча задач, где DSL отличный вариант, а где-то не нужен. Если надо проехать 100 км, то автомобиль явно предпочтительнее самолета, а если надо во Владивосток, то наверно лучше на самолете. Все равно это будет вопросом целесообразности и личного вкуса.

Dasar

Из того, что некто говорит, что он реализует FRP на ООП не следует, что это действительно так. В реальности это может быть реализация РП, а Ф там появилась из-за того, что автор столкнулся с РП при использовании ФП,
Например, та же иммутабельность не является прерогативой ФП, но при этом сплошь и рядом встречаются утверждения: мы использовали иммутабельность из ФП.

Dasar

Есть куча задач, где DSL отличный вариант
C DSL последнее время много носились, и мне они тоже местами нравятся, но последнее время восторги поутихли из-за того, что DSL имеет большой мешок минусов - их не поддерживает IDE и вспомогательные утилиты. Даже с человеческим описанием синтаксической ошибки бывают проблемы. Были надежды, что это можно автоматизировать, но пока существенных подвижек не видно.
Вот в приведенном коде, что будет, если вносить различные ошибки в DSL?
При этом, каким образом они будут вызываться - дело десятое, на сложность это не повлияет.
Как эта проблема будет разрешаться?
ООП предлагает внятный алгоритм: считаем, что каждая ФС есть черный ящик с изменяемым состоянием, но все они описываются одним контрактом. И поверх этого контракта можно записать большую часть операций единообразным способом, что и убирает комбинаторный взрыв.

Ivan8209

> Из того, что некто говорит, что он реализует FRP на ООП
> не следует, что это действительно так.
Открываю любую из указанных статей и вижу, что они честно
пытаются слизать код с хаскела. На каком основании я должен
верить тебе, а не авторам?
---
"Дебилы, несмотря на замедленность и конкретность мышления,
низкий уровень суждений, узкий кругозор, бедный запас слов
и слабую память, способны к приобретению некоторых знаний
и профессиональных навыков."

Dasar

Реализация РП ортогональна ФП или ООП, поэтому код почти один в один переносится из ФП в ООП.
ps
Рекурсивные алгоритмы, например, также один в один можно перенести между ФП и ООП.

Ivan8209

> ООП предлагает внятный алгоритм: считаем, что каждая ФС есть
> черный ящик с изменяемым состоянием, но все они описываются
> одним контрактом.
А потом обнаруживаем, что этот "контракт" ничего не стоит,
так как одни реализуют переименование атомарно, а другие нет.
Ну, а кроме того, в этом твоём примере ФС явно представляет
собой объект абстрактного линейного типа, что заметно слабее
твоей ООП.
---
Моё знакомство с ООП началось со слова "интифада."

ramsit

DSL имеет большой мешок минусов - их не поддерживает IDE и вспомогательные утилиты.
wut?
вообщето, DSL это просто код на каком-то языке, и "поддерживать" его должен компилятор/интерпретатор. Причем тут IDE - непонятно.
Вот в приведенном коде, что будет, если вносить различные ошибки в DSL?
Что будет, если бросить лом в унитаз вагона на полном ходу? Ничего хорошего, но зачем? :)
Как эта проблема будет разрешаться?
ООП предлагает внятный алгоритм: считаем, что каждая ФС есть черный ящик с изменяемым состоянием, но все они описываются одним контрактом. И поверх этого контракта можно записать большую часть операций единообразным способом, что и убирает комбинаторный взрыв.
Кодогенерация в рантайме с помощью макросов. Для каждого типа действий с каждой ФС пишется свой макрос, плюс общий макрос-диспетчер по метке в структуре данных. Это несложно и вполне реализуемо, хотя строго говоря это тоже весьма похоже на самодельный ООП, так сказать, на "обобщенных макросах" :)
Стало быть, тут действительно проще воспользоваться готовым решением и не городить огород, но строго говоря, использование классов и методов не единственный вариант
(кстати плюсом диспатчинга на макросах является отсутсвие издержек на работу с объектами - это довольно тяжелая вычислительная работа, если обращения к методам и слотам объектов происходят очень часто, это будет заметно)

Dasar

Причем тут IDE - непонятно.
debugger, например, тоже не нужен?
Ничего хорошего, но зачем? :)
Так и запишем, что DSL подходит только для программистов, которые никогда не ошибаются, т.е. только для роботов.

ramsit

debugger, например, тоже не нужен?
Ну ок, согласен, есть некоторые траблы. Впрочем, DSL-подход характерен для интерактивных языков, а не статически-компилируемых, а там подходы к дебаггингу несколько иные, например, в 95% случаев хватает тупой отладочной печати и трассировки вызовов.
Так и запишем, что DSL подходит только для программистов, которые никогда не ошибаются, т.е. только для роботов.
Не вижу принципиальной разницы между ошибкой в DSL и, скажем, в сторонней библиотеке. В DSl ошибаться нельзя, а в любом другом коде как будто можно :)

Dasar

В DSl ошибаться нельзя, а в любом другом коде как будто можно
Ошибку в обычном коде сначала подсветит IDE, затем по человечески ругнется компилятор, далее пожурит валидатор кода, и на крайняк можно посмотреть под debugger-ом что происходит.
В DSL-е всего этого нет, либо придется затратить кучу человеко-лет, чтобы всё это появилось.

Ivan8209

> В DSL-е всего этого нет, либо придется затратить кучу человеко-лет,
> чтобы всё это появилось.
Чё?
---
"Narrowness of experience leads to narrowness of imagination."

ramsit

Ошибку в обычном коде сначала подсветит IDE, затем по человечески ругнется компилятор, далее пожурит валидатор кода, и на крайняк можно посмотреть под debugger-ом что происходит.
В DSL-е всего этого нет, либо придется затратить кучу человеко-лет, чтобы всё это появилось.
Пользоваться валидатором кода считаю зазорным, т.к. писать изначально синтаксически корректный код - обязанность любого нормального погромиста.
Подсветка есть в любом програмерском редакторе.
Почему debugger с dsl должен работать как-то иначе, я не понимаю. Он точно так же выдаст backtrace вызовов, включая вызовы макросов. В остальном, dsl - это самый обычный код с точки зрения компилятора/интерпретатора
В DSL-е всего этого нет
DSL - это самый обычный код, компилятор не видит никакой принципиальной разницы между ним и любым другим кодом

Dasar

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

Dasar

В остальном, dsl - это самый обычный код с точки зрения компилятора/интерпретатора
DSL не является обычным кодом из-за того, что появляется дополнительное преобразование: DSL -> код.
Для того, чтобы появились человеческие ошибки, debugger и т.д. необходимо поддержать обратное преобразование код -> DSL, что и требует дохера вложений.

ramsit

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

Ivan8209

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

ramsit

DSL не является обычным кодом из-за того, что появляется дополнительное преобразование: DSL -> код.
Для того, чтобы появились человеческие ошибки, debugger и т.д. необходимо поддержать обратное преобразование код -> DSL, что и требует дохера вложений.
не вижу разницы между DSL на макросах и обычным использованием макросов. За пафосным словом DSL скрывается всего лишь системное использование макросов.
И опять же, чисто практический аргумент. сколько разных DSL-ек я ни делал, каких-то особых проблем с отладкой не испытывал. Всегда есть возможность посмотреть сгенерированный код, а больше ничего и не надо
Кстати, надо всегда держать в уме, что то, что можно сделать с помощью макросов, *всегда* можно сделать и без них, так что все вышеперечисленное более-менее справедливо не только для лиспа, но, скажем, для питона. (хотя никому в здравом уме не придет в голову ваять подобное на питоне)

Dasar

DSL-код генерится в рантайме, дебаггер все прекрасно видит и выдает полный бэктрейс в перемешку вызовов dsl-функций и производящих их макросов
Откуда в стеке будут производящие макросы, если сначала происходит выход из производящих макросов, а только потом выполняется сгенеренный код?

ramsit

Откуда в стеке будут производящие макросы, если сначала происходит выход из производящих макросов, а только потом выполняется сгенеренный код?
НЕТ
вызов и раскрытие макроса происходит в рантайме ровно в том месте, где он вызывается, поэтому дерево вызовов не нарушается

Dasar

тогда это интерпретация, а не трансляция - что сильно скажется на производительности.

ramsit

тогда это интерпретация, а не трансляция - что сильно скажется на производительности.
Это полноценная компиляция в машинный код на лету. ЭТО СПАРТА!
интересно, через сколько десятилетий эту фичу представят на ждаве или дотнете как принципиально новую и прорывную? :)
ЗЫ. Нода.js таки уже доросла, и там даже кой-какие макросы есть, считай можно кодить эффективные DSL на ноде

Dasar

Это полноценная компиляция в машинный код на лету. ЭТО СПАРТА!
это другое, и ортогонально обсуждаемой теме. В машинный код может переводиться и интерпретатор, и транслятор, и результат трансляции.
на псевдокоде это выглядит так

result интерпретатор(dsl, входные-данные)
{
var current = входные-данные
для каждой инструкции dsl
сurrent = код(current)
return current;
}
//основная программа
stdout = интерпретатор(dsl, stdin)

код транслятор(dsl)
{
каждую инструкцию dsl
преобразовать в код
}
//основная программа
var код = транслятор(dsl)
stdout = код(stdin)

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

//интерпретация при использовании компилятора
var маш-код-интерпретатора = компилятор(интерпретатор)
stdout = маш-код-интерпретора(dsl, stdin)

//маш-код-интерпретатора выглядит так после компиляции
result маш-код-интерпретатора(dsl, входные-данные)
{
var current = входные-данные
для каждой инструкции dsl
сurrent = маш-код(current)
return current;
}

//трансляция при использовании компилятора
var маш-код-транслятора = компиляция(транслятор);
var код = маш-код-транслятора(dsl)
var маш-код = компиляция(код);
stdout = маш-код(stdin)

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

Hastya

Никакая парадигма не может уменьшить внешнюю сложность задачи. Если есть полсотни видов widget-ов с различным поведением, или полсотни разных видов коллекций с различным поведением, или полсотнки видов ФС с различным поведением, то никакой DSL это не уменьшит.
Так DSL имхо и не предназначен для борьбы со сложностью. Это скорее механизм переноса сложности от одних программистов к другим, т.е. просто еще один вариант layering.

ramsit

это другое, и ортогонально обсуждаемой теме. В машинный код может переводиться и интерпретатор, и транслятор, и результат трансляции.
интерпретатор преобразует в байткод (псевдокод) виртуальной машины, а затем он транслируется в машкод конкретной железкии и исполняется. Это путь дотнета, джавы и т.д.
Я говорю про прямую компиляцию исходного кода в рантайме в машинный код, минуя байткод.
Очевидно, у джавы и дотнета не может быть компиляции в рантайме, потому что конпеляция в байт-код делается еще в compile-time (это не интерптеритуемые языки).
Эта фича есть только в SBCL common lisp и в node.js с небольшой натяжкой, ты просто не знаком с SBCL и поэтому не понял, о чем речь :)
Грубо говоря, SBCL ведет себя как самый обычный интерптетатор типа питона или перла, но псевдокодом ее виртуальной машины является оптимизированный машинный код текущей железки
Макросы в лиспе раскрываются еще до начала стадии "переваривания" кода (интерпретации, трансляции или конпеляции а еще на стадии формирования абстрактно-синтаксического дерева (AST поэтому компилятора уже нет никакой разницы между нераскрытыми макросами и результатом их раскрытия.
Так что, wake up, Neo :)
Вообще, фтопку это словоблудие, я делал полноценные DSL и что ни с чем вышеперечисленным (потери производительности, трудности в отладке и т.д.) не сталкивался

ramsit

Так DSL имхо и не предназначен для борьбы со сложностью. Это скорее механизм переноса сложности от одних программистов к другим, т.е. просто еще один вариант layering.
Тащем-то да, dsl - это всего лишь yet another прослойка абстракции.
Грамотно сделаннная прослойка абстрации саму задачу может и не упростит, но вот кодинг этой задачи - вполне может.

Dasar

Очевидно, у джавы и дотнета не может быть компиляции в рантайме, потому что конпеляция в байт-код делается еще в compile-time (это не интерптеритуемые языки).
Не очевидно. Это не очевидно для языков с возможностями подгрузки кода в runtime-е и поддержкой reflection-а.
Есть целых два пути:
1. Запустить компиляцию в рантайме, и полученный byte-код подгрузить динамически
2. Сгенерить сразу byte-код
Оба варианта возможны и в Java, и в .Net-е.
По первому варианту, в .net-е с рождения так были реализованы regexp-ы (самый распространенный dsl) и xml-сериализация.
Есть еще третий вариант - генерить сразу маш-код, так делают либы по поддержке SIMD-операций и GPU-операций.
Сейчас с выходом Roslyn-а появился четвертый вариант: в runtimе-е можно генерить сразу AST (раньше это был текст) и отдавать его на компиляцию с последующим исполнением.
Макросы в лиспе раскрываются еще до начала стадии "переваривания" кода (интерпретации, трансляции или конпеляции а еще на стадии формирования абстрактно-синтаксического дерева (AST поэтому компилятора уже нет никакой разницы между нераскрытыми макросами и результатом их раскрытия.
Это известная штука, и есть не только в lisp-е. Так сделано, например, в Nemerle - язычок для .net, расширяющий c# поддержкой compile-макросов на c#.
Ты не уловил самое главное - разницу между трансляцией и интерпретацией, цепляясь за то, что sbcl весь из себя исключительный. Это не так. Всё тоже самое есть и в других языках.
Я бы показал в чем разница между интерпретатором и компилятором на лиспе, но хоть я его и знаю - мне лень его запускать, он меня выбешивает своим скобочным-синтаксисом.
На C#-е могу легко показать и разницу, и какие проблемы появляются. Подчеркну что эти проблемы самого подхода (dsl-я, интерпретации, трансляции а не используемого языка.
я делал полноценные DSL и что ни с чем вышеперечисленным (потери производительности, трудности в отладке и т.д.) не сталкивался
ты всё это измерял?

ramsit

1. Запустить компиляцию в рантайме, и полученный byte-код подгрузить динамически
2. Сгенерить сразу byte-код
байткод, а не машкод
sbcl сразу все компилит в машкод
Ты не уловил самое главное - разницу между трансляцией и интерпретацией,
Интерпретация - генерация байткода, трансляция - генерация машкода из байткода. Правильно? Опять эти термины :)
Я бы показал в чем разница между интерпретатором и компилятором на лиспе, но хоть я его и знаю - мне лень его запускать,
Я разбирал по крупицам устройство компилятора SBCL, писал патчи, так что мне не надо ничего объяснять :)
ты всё это измерял?
Что измерял? Я точно знаю на своем опыте факт, что отладка макросов по крайней мере в SBCL ничем не сложнее отладки обычных функций, а так же то, что использование макросов не сказывается на производительности, причем я тебе это уже обосновал.
Еще раз, dsl не влияет на дебаггинг, потому что макросы раскрываются в рантайме, и не влияет на производительность, потому что макросы раскрываются при построении AST

Dasar

байткод, а не машкод
sbcl сразу все компилит в машкод
в чем ты видишь существенное преимущество между одним вызовом "маш-код = трансляция(код)" и двумя "маш-код = трансляция(трансляция(код"? У второй, конечно, теоретически должно быть чуть больше время трансляции, но на практике это различие нивелируется другими факторами.
Интерпретация - генерация байткода, трансляция - генерация машкода из байткода. Правильно? Опять эти термины
нет.
Интерпретация и трансляция - это виды перевода из одних команд в другие
Основое отличие я приводил выше

result интерпретатор(высокоуровневый-код, входные-данные)
{
var current = входные-данные
для каждой инструкции dsl
сurrent = низкоуровневый-код(current)
return current;
}
//основная программа
stdout = интерпретатор(высокоуровневый-код, stdin)

низкоуровневый-код транслятор(высокоуровневый-код)
{
каждую инструкцию высокоуровневый-код
преобразовать в низкоуровневый-код
}
//основная программа
var низкоуровневый-код = транслятор(высокоуровневый-код)
stdout = низкоуровневый-код(stdin)

И соответственно, каждое из преобразований (код -> byte-код, byte-код -> маш. код, код -> маш. код, dsl -> код) может быть трансляционным, а может быть интерпретацией.
в .net и в java-е преобразования код -> byte-код и byte-код -> маш-код делаются через трансляцию. Преобразование Dsl -> код также чаще всего делается через трансляцию.
Что измерял?
Сколько времени другой человек(не автор dsl-я) тратит на исправление ошибки в dsl-е, и в обычном коде?
За сколько времени отрабатывает код с dsl-ем и без?
Сколько памяти съедается при использовании dsl-я и без?

Dasar

Он точно так же выдаст backtrace вызовов, включая вызовы макросов.
Покажи backtrace для приведенного помощника "скрепки", чтобы там были одновременно вызовы dsl-я и сгенеренного кода

apl13

Если кто-то из тех, кто говорит, что без ООП нельзя ступить ни шагу, сделает то же самое на своем любимом языке
А ты специально для демонстрации того, что ООП не нужно, выбрал Common Lisp?

ramsit

Покажи backtrace для приведенного помощника "скрепки", чтобы там были одновременно вызовы dsl-я и сгенеренного кода
Для модельности на примере отдельного макроса:
> (defmacro buggy (x)
`(exp (/ 1 ,x

> (buggy 1)
2.7182817

> (buggy 0)
arithmetic error DIVISION-BY-ZERO signalled
Operation was SB-KERNEL::DIVISION, operands (1 0).
[Condition of type DIVISION-BY-ZERO]

Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "new-repl-thread" RUNNING {1005BCC8E3}>)

Backtrace:
0: (SB-KERNEL::INTEGER-/-INTEGER 1 0)
1: DEFMACRO BUGGY) (BUGGY 0) #<unavailable argument>)
2: (MACROEXPAND-1 (BUGGY 0) #<NULL-LEXENV>)
3: (MACROEXPAND (BUGGY 0) #<NULL-LEXENV>)
4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (BUGGY 0) #<NULL-LEXENV>)
5: (EVAL (BUGGY 0

Можем нажать на любой пункт backtrace и посмотреть аргументы и лексическое окружение вызова, а также код каждого пункта
macroexpand и macroexpand-1 - это непосредственно операции раскрытия макроса.
Их можно вызвать из REPL вручную и посмотреть сгенерированный макросом код, потом запустить его. Ну это если совсем припрет

ramsit

А ты специально для демонстрации того, что ООП не нужно, выбрал Common Lisp?
Еще раз, я не демонстрирую, будто ООП не нужен.
Я тут выше не раз говорил, что ООП это хорошо и клево, особенно в нужном месте в нужное время.
Но не нужно делать из ООП эдакий абслютный must use
а CL - это просто вершина современного языкостроения, поэтому на нем и примеры.

ramsit

Сколько времени другой человек(не автор dsl-я) тратит на исправление ошибки в dsl-е, и в обычном коде?
Зависит на 99% только от радиуса кривизны рук автора и читаемости кода.
За сколько времени отрабатывает код с dsl-ем и без?
Сколько памяти съедается при использовании dsl-я и без?
плюс-минус одинаково.
раскрытие макроса делается один раз, это нечастая процедура и узким местом не является
с точки зрения производительности исполнения самого кода макроса, использование макроса полностью эквивалентно декларации inline со всеми вытекающими последствиями: если этот код вызывается часто, то это +производительность, если код имеет большой размер, то это минус память.
Как правило, это несчастные доли процента в большинстве случаев, как прирост производительности, так и рост использования памяти

Dasar

Для модельности на примере отдельного макроса:
Здесь у тебя макросы используются в режиме интерпретации, а в коде, который ты приводил изначально, в режиме трансляции.
Поэтому приведи backtrace для кода из начальной ссылки.

Dasar

Это показывает, что ты ничего не знаешь, кроме таких убогих языков, где надо внимательно следить за этими вещами.
Проблема выхода за пределы коллекций есть во всех языках. В языках с зависимыми типами эту валидацию можно перенести на сам язык, но саму проблему это не отменяет.
Мертвые указатели - согласен. это больше проблема отсутствия gc или использования системы типов, в которой null считается нормальным значением любой ссылки.
Но это только верхушка айсберга, которую может проверять валидатор кода. Также валидатор может искать зацикливания, опечатки, неоптимальное использование алгоритмов и т.д.

ramsit

Здесь у тебя макросы используются в режиме интерпретации, а в коде, который ты приводил изначально, в режиме трансляции.
Поэтому приведи backtrace для кода из начальной ссылки.
Да нету в CL никаких режимов трансляции и интерптретации!
Есть раскрытие макросов при построении AST-дерева, и просто компиляция в машкод.
ок, щас приведу пример из скрепки

ramsit

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

Dasar

Да нету в CL никаких режимов трансляции и интерптретации!
в макросе есть (точнее в стиле написания кода). Макрос может свое тело раскрывать трансляционно, а может в виде интерпретации.
Как напишешь, так и будет.

Dasar

Это должен делать человек, c помощью прямых рук и юнит-тестов. Всякие решарперы и валидаторы - для code-monkey
Зачем делать самому то, что можно поручить компьютеру?

ramsit

пример из скрепки, вставил деление на ноль в код, который генерится макросом, который генерится другим макросом:
arithmetic error DIVISION-BY-ZERO signalled
Operation was SB-KERNEL::DIVISION, operands (1 0).
[Condition of type DIVISION-BY-ZERO]

Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT] Abort thread (#<THREAD "new-repl-thread" RUNNING {1005DD4EA3}>)

Backtrace:
0: (SB-KERNEL::INTEGER-/-INTEGER 1 0)
1: (/ 1 0)
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (/ 1 (RANDOM 1 #<NULL-LEXENV>)
3: (SB-IMPL::SIMPLE-EVAL-PROGN-BODY DEFUN LESSER (CARD-A CARD-B) (OR (OR (AND (EQ CARD-A (QUOTE AS (OR (EQ CARD-B (QUOTE |2S| (EQ CARD-B (QUOTE |2C| (EQ CARD-B (QUOTE |2H| (EQ CARD-B (QUOTE |2D..
4: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SLT/DEFCARDS (SUITES (S C H D (RANKS (A 2 3 4 5 6 7 8 9 T J Q K (COLORS BLACK (S C (RED (H D #<NULL-LEXENV>)
5: (EVAL (SLT/DEFCARDS (SUITES (S C H D (RANKS (A 2 3 4 5 6 7 8 9 T J Q K (COLORS BLACK (S C (RED (H D

Так как это двойное вложение, то defmacro тут не видно, но все равно есть полный код вызовов

ramsit

в макросе есть (точнее в стиле написания кода). Макрос может свое тело раскрывать трансляционно, а может в виде интерпретации.
Как напишешь, так и будет.
Блин, ну бред какой-то, чесслово.
Макрос раскрывается тольво одним способом - через defmacro, и нет там никаких режимов. Лисповые макросы - это немного особая вещь
Зачем делать самому то, что можно поручить компьютеру?
С одной стороны да, с другой - результаты работы всяких решарперов мы видели :)

Dasar

вставь ошибку в def-rule.
например, рядом с move-top в правиле
(slt/defrule pile s stock) (pile w waste) (top-card c (on s) unknown
  (reveal-top s)
  (move-top s w

Ivan8209

>> Это показывает, что ты ничего не знаешь, кроме таких убогих
>> языков, где надо внимательно следить за этими вещами.
> Проблема выхода за пределы коллекций есть во всех языках.
Да ну? Расскажи мне, как мне выйти за предел коллекции
в том же лиспе или хаскеле. На выбор.
> Также валидатор может искать зацикливания, опечатки,
И с поиском опечаток, и с поиском зацикливаний, если они очевидны,
интерпретатор справляется лучше валидатора.
Или ты научился легко решать проблему останова?
> неоптимальное использование алгоритмов и т.д.
Если ты видишь неоптимальное использование, то ты знаешь,
что надо использовать взамен. В этом случае надо не заниматься
ерундой, а просто оптимизировать, и опять интерпретатор
справляется с этим лучше, чем "валидатор."
---
CONTRA FACTVM NON DATVR ARGVMENTVM

Dasar

Да ну? Расскажи мне, как мне выйти за предел коллекции
в том же лиспе или хаскеле. На выбор.
Тут есть два варианта: либо используются простые коллекции с большим кол-вом кода и тогда выйти не получится, либо используются сложные коллекции с малым кол-вом кода, но тогда желательно код валидировать на отсутствие runtime-проблем.
Или ты научился легко решать проблему останова?
Из того, что проблема останова не решается в общем виде не следует, что она не решается в частных случаях.
Если ты видишь неоптимальное использование, то ты знаешь, что надо использовать взамен.
Такое есть не всегда - как минимум из-за того, что код детекторов проблем много проще, чем код для конструирования решения.

Dmitriy82

> Расскажи мне, как мне выйти за предел коллекции
в том же лиспе или хаскеле.
[] ! 0

Ivan8209

>> Да ну? Расскажи мне, как мне выйти за предел коллекции
>> в том же лиспе или хаскеле. На выбор.
> Тут есть два варианта: либо используются простые коллекции
> с большим кол-вом кода и тогда выйти не получится,
Тебе не кажется, что тогда проверять и не надо?
> либо используются сложные коллекции с малым кол-вом кода,
> но тогда желательно код валидировать на отсутствие runtime-проблем.
Что ты называешь "runtime проблемой?"
Если у тебя возникают проблемы в твоей реализации языка, то тебе
надо исправлять ошибку в реализации, только при чём здесь "валидатор?"
Если ты говоришь про исключения, возникающие при коде наподобие

sub (array (0,0 0);

то там проблемы уже решены, за этим и вызывают обработчик исключений,
иначе бы у операции "sub" была другая сигнатура, скорее всего ---
'a array * int -> 'a option, а не 'a array * int -> 'a.
>> Если ты видишь неоптимальное использование,
>> то ты знаешь, что надо использовать взамен.
> Такое есть не всегда
Что значит "не всегда?"
Либо у тебя есть решение лучше, но тогда ты его можешь построить,
а значит, что надо просто не пудрить мозг, а поправить оптимизатор,
либо у тебя нет решения, а тогда и разговор ни о чём.
> как минимум из-за того, что код детекторов проблем много проще,
> чем код для конструирования решения.
Много этих "детекторов" видел?
Твой "валидатор" --- это самый настоящий оптимизатор: если
ты сумел доказать, что твой индекс всегда попадает в доступные,
то ты доказал существование мёртвой ветки в коде. Это означает,
что от просто валидатора толку значительно меньше, чем если его
переделать в оптимизатор.
---
"Университет развивает все способности, в том числе и глупость."

yroslavasako

он меня выбешивает своим скобочным-синтаксисом.
вам шашечки или ехать? Если что, заменить скобочки на отступы несложно, я видел несколько разных утилит для этого. Вот например: http://www.drachentr&#228;nen.de/light/english/wisp-lisp... Вроде ещё плагины для IDE есть с тем же результатом.

Dasar

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

Dasar

Что ты называешь "runtime проблемой?"
Runtime-проблема - это какая-либо ситуация, появление которой мы не хотим получить в runtime. Есть ли при этом runtime-обработчик этой ситуации или нет - к делу это не относится. Пример, на пальцах: мы не хотим, чтобы поведение программы привело к разбалансировке реактора, то что в коде при этом есть обработчик ситуаций, когда реактор расбалансирован - не является решением этой проблемы.
Сам язык (без валидатора) может лишь гарантировать, что на каждую возможную ситуацию в runtime есть свой обработчик. Гарантировать, что поведение программы не может привести к нежелательной ситуации, он при этом не может. (Исключением являются языки с зависимыми типами, но это пока редкость, еще большая редкость зависимые типы с мощным type inference)
Либо у тебя есть решение лучше, но тогда ты его можешь построить,
а значит, что надо просто не пудрить мозг, а поправить оптимизатор,
либо у тебя нет решения, а тогда и разговор ни о чём.
Например, валидатор может выдать, что код имеет экспоненциальную сложность(2^N) и при этом знает, что N на реальных задачах достигает более 100.
Это уже показывает, что код надо выбрасывать в мусорку, но конструктивного автоматического решения очевидно здесь не будет, т.к. частое решение этой проблемы - ослабление условий постановки задач.
Или валидатор может заметить, что используемый алгоритм плохо параллелится из-за того, что используется последовательная схема расчетов. И соответственно, выигрыша от 16 ядер никакого не будет. Преобразование последовательной схемы в параллельную нетривиальная задача.

Dasar

Макрос раскрывается тольво одним способом - через defmacro, и нет там никаких режимов.
Так и про функцию можно сказать, что функция просто вызывается и нет там никаких режимов.
Но при этом на функциях делаются очень различные вещи. Монада, например, это тоже лишь функция.
ps
в defrule ошибку вставил (в раздел команд)? пости что получилось, интересно же.

karkar

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

А, просто ты перепутал DSL и eDSL (aka DSEL) - embedded DSL. Вот makefile - это DSL. SQL - DSL. RegExp - DSL. А то, о чем ты говоришь, - это embedded DSL.

karkar

 
Да ну? Расскажи мне, как мне выйти за предел коллекции
в том же лиспе или хаскеле.

В говнохаскеле head [] бросает исключение, хотя это даже не помечено никак в типе head.
И с поиском опечаток ... интерпретатор справляется лучше валидатора.

Оставить комментарий
Имя или ник:
Комментарий: