LINUX.ORG.RU

Анализ пользователей Common Lisp и Racket

 , ,


11

7

Common Lisp разрабатывался и используется в предположении, что пользователь программы — программист. Поэтому из языка намеренно исключены сложные для понимания конструкции (пользователь не обязательно квалифицированный программист), поэтому в языке мощнейший отладчик, позволяющий без остановки программы переопределять функции и вообще делать что угодно. Но из-за этого документация по большей части библиотек Common Lisp существует только в виде docstring и комментариев в коде (некоторые вообще считают, что код сам себе документация). Из-за этого обработка ошибок почти всегда оставляется на отладчик (главное сделать рестарт «перезапустить с последней итерации», а там пользователь сам разберётся). Из-за этого в программе проверяется только happy path (пользователь ведь «тоже программист»).

Racket разрабатывался и используется в предположении, что пользователь программы не программист, а задача разработчика написать программу так, чтобы она корректно работала при любых входных данных (если данные некорректны, то сообщала об этом в том месте, где данные были введены). Поэтому в языке эффективная библиотека для написания тестов, система контрактов на уровне модулей, макимально широкий спектр инструментов программирования (разработчик должен быть профессионалом!). Также реализована идея инкапсуляции: считается, что пользователь модуля не должен знать особенности реализации и, более того, не может в своём коде изменить функцию чужого модуля если это явно не разрешено разработчиком того модуля. Исходный код разумеется доступен, но его не требуется смотреть, чтобы использовать модуль. Достаточно документации. Поэтому реализована мощнейшая система документировния Scribble, а при реализации макроса есть возможность обеспечить указание на ошибки в коде, предоставленном макросу пользователем, не показывая потроха макроса.

И поэтому в Racket нет CLOS (есть как минимум две реализации, но не используются) - провоцирует заплаточное программирование (monkey patching), поэтому отладчик намеренно ограничен (если ты отлаживаешь программу, значит ты не знаешь как она должна работать!), поэтому нет разработки в образе (image based) - она провоцирует разработку через отладку (а значит непонимание программы и проверку только happy path).

Таким образом, Racket и Common Lisp несмотря на внешнее сходство являются очень разными языками. И я рекомендую писать на Racket, если только конечными пользователями программы не являются исключительно программисты на Common Lisp.

Взято с http://racket-lang.blog.ru/#post214726099

Хотелось бы знать, что по этому поводу думают пользователи ЛОРа. А также, мне кажется, что для Java и C++ будет где-то такая же разница.

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

При выводе типов, компилятор в haskell не путытается ничего угадывать, он выводит, при этом он выводит исключительно правильно.

Как ты можешь говорить о том, что компилятор выводит правильно, если тебе предоставлен пример, в котором он вывел тип НЕПРАВИЛЬНО (a String -> Int вместо String -> Int)?

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

Это всем понятно. Тебе и пытаются объяснить (на достаточно простых примерах), что этот алгоритм НЕ ГАРАНТИРУЕТ правильного вывода. Часто тип, выведенный в соответствии с этим алгоритмом - НЕВЕРЕН. Что тебе тут непонятного?

В твоём примере: ты написал функцию с типом t -> String -> Int,

В том и дело, что он написал ф-ю с типом String -> Int. Но написал ее _неправильно_. В реализации ф-и была допущена ошибка. Компилятор попытался вывести тип на основе ошибочного кода - и получил неправильный тип.

Ошибка по факту сделана в модуле, где определен strlen. Компилятор вместо этого указывает ошибку совершенно в другом модуле и советует доопределить инстанс show. Ну, допустим, я его послушаю и доопределю инстанс show соответствующий, программа станет правильно работать? НЕ СТАНЕТ. Потому что ошибка не в show size - это как раз правильный кусок кода.

в силу чистоты и referece transparancy это сделать сравнительно просто.

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

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

в том модули нет спецификации функции, нечему соответствовать

Именно! По-этому чекер проверяет на соответствие своим влажным фантазиям. А должен - потребовать у программиста спецификацию, не монетку подкидывать.

Пожалуйста, прекращай уже дурить, мне надоело.

На данный момент под дурачка косишь ты, делая вид, что не понимаешь очевидных вещей. Всем понятно какая ошибка была допущена и где (с-но монк в самом начале это пояснил) и надо быть полным кретином, чтобы пытаться тут кого-то убеждать, что strlen написана правильно и проблема не в ней, а в том что у String -> Int инстанса show нету.

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

Объясню тебе чтоб уж совсем как ребенку, вот смотри:

(define/contract (first lst)
  ((and/c list? (not/c null?)) . -> . any)
  (car lst))
теперь если я сделаю:
> (first 1)
. . first: contract violation
  expected: (and/c list? (not/c null?))
  given: 1
  which isn't: list?
  in: the 1st argument of
      (-> (and/c list? (not/c null?)) any)
  contract from: (function first)
  blaming: anonymous-module
  at: unsaved-editor808:3.18
> 
показывает ошибку в ф-и first - туда надо передавать список, а передали не-список. Теперь:
(define (first lst)
  (car lst))
->
> (first 1)
. . car: contract violation
  expected: pair?
  given: 1
> 
Ошибка та же самая - в first. Но пишет что она на самом деле в car. Хотя она не в car, она в first.

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

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

понятно. Ты просто балабол и враль. И явно не тебе учить матчасти человека, который эту матчасть знает на порядок лучше тебя. Советую тебе перестать позориться и разобраться, как работает вывод типов.

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

Я бы сказал, что юнит-тесты потому можно не считать «запуском программы», что их легко автоматизировать и включить в скрипт сборки, тем самым снизив трудоёмкость их поддержания.

Возвращаясь к тому с чего я начал, давайте всё же согласимся, что статическая типизация и динамическая реализация друг другу не противоречат.

Динамическая реализация очень полезна (я об этом много раз писал). Например, во всех SQL-серверах есть возможность подать запрос с консоли, т.е. eval, а также alter table и alter procedure, т.е., SQL реализован динамически. Посмотрите, как широко испольузется SQL. Также сама операционная система является динамической реализацией, если считать исполняемые файлы функциями. Можно в любой момент создать новую программу в операционной системе и во многих случаях можно заменить старую программу, не требуя перезапуска или пересборки всей операционной системы. Модули ядра Linux также являются примером динамической реализации, хотя здесь реализация должна удовлетворять определённому интерфейсу - сам интерфейс на ходу не поменять.

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

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

Поэтому именно из соображений производительности труда я призываю пользоваться динамическими реализациями, а не статическими. Поэтому я за Common Lisp и против Racket. К сожалению, нет истинно динамической Java, C# и C, поэтому я вообще предпочитаю Common Lisp другим языкам, несмотря на его недостатки.

По другим ответам на мои комменты - я не дискутирую с анонимусом. Хотите пообщаться - зарегистрируйтесь.

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

Например, во всех SQL-серверах есть возможность подать запрос с консоли, т.е. eval, а также alter table и alter procedure, т.е., SQL реализован динамически. Посмотрите, как широко испольузется SQL. Также сама операционная система является динамической реализацией, если считать исполняемые файлы функциями.

И часто пользователь операционной системы (не программист) изменяет системные файлы или пользователь информационной системы на SQL меняет таблицы (и особенно процедуры)?

Чем больше программа, тем больше накладные расходы на её пересборку.

Пересобираются только изменённые модули и те модули, которые от них зависят. Поэтому, если базовые модули написаны корректно, то накладные расходы на пересборку невелики. Динамическая же замена на лету напоминает kexec для обновления ядра (чтобы uptime не слетал). В случае более-менее существенных изменений, ломается состояние системы.

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

В этом смысле Racket 100% динамический. Для запуска любого кода на базе существующего достаточно стандартного REPL'а. Нельзя только изменять импортированные из других модулей объекты. Но для плагинов такое ограничение — необходимость. Новую версию модуля также можно загрузить на ходу.

> (enter! "m.rkt")
> (define tt (a 5))
> tt
6
> (enter! "m.rkt")
  [re-loading /home/monk/languages/racket/test/m.rkt]
> tt
6
> (a 5)
4

Поэтому именно из соображений производительности труда я призываю пользоваться динамическими реализациями, а не статическими. Поэтому я за Common Lisp и против Racket.

Из обоснованных аргументов только идея «язык как операционная система с shell». Из которой вытекает идея разработки в образе.

Но я именно поэтому буду против. :-) Common Lisp для пользователя программы - это как UNIX с одним пользователем: root. Пользватель может изменить любой объект и с лёгкостью сломать систему. Причём всё средства защиты на уровне UNIX'овых «невидимых файлов». Если файл начинается с точки, то он невидимый, если символ в пакете доступен через два двоеточия, значит он внутренний.

monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от anonymous

Что-то твой динамизм не сильно помог популяризации

Популяризации Линукса вообще ничего не помогало. Пока не пришёл Шаттлворт и не вбухал кучу денег в рекламу.

А популяризации Windows 95 не помешала её убогость даже на фоне (тогда существовавших) Mac OS и OS/2.

Техническое совершенство и популяризация — понятия перпендикулярные.

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

Пока не пришёл Шаттлворт и не вбухал кучу денег в рекламу.

Он сделал дистрибутив, который в 90% случаев юзабелен сразу из коробки без конпеляции-консоли-конфигов, и при этом не выглядит как говно. Хотя красноглазики в упор это не замечают, ведь в их глазах какой-нибудь yast - предел мечтаний для обычного пользователя.

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

Он сделал дистрибутив, который в 90% случаев юзабелен сразу из коробки без конпеляции-консоли-конфигов

Да ладно! До него были RedHat и SuSE. А ещё для домашнего пользователя Mandrake.

Ubuntu выехал исключительно на рекламе. От Debian Testing отличия были минимальные.

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

И часто пользователь операционной системы (не программист) изменяет системные файлы или пользователь информационной системы на SQL меняет таблицы (и особенно процедуры)?

Если я ничего не путаю, то мы говорили о производительности труда именно программиста.

Пересобираются только изменённые модули и те модули, которые от них зависят. Поэтому, если базовые модули написаны корректно, то накладные расходы на пересборку невелики.

Кроме расходов на пересборку, есть ещё и расходы на перезапуск. Понятие «базовых модулей» выглядит необоснованно придуманным.

В этом смысле Racket 100% динамический. ... Нельзя только изменять импортированные из других модулей объекты.

Насколько я понял, ты говоришь, что Racket не хуже перечисленных мной «костылей». Но это не то, чем следовало бы хвалиться.

Возможность изменять на ходу одну функцию, переменную, определение класса независимо от места их определения - это основа процесса разработки в CL. Бывают случаи, когда это нежелательно. У меня был один такой случай. Для него я написал обёртку к команде IDE compile defun (кстати, я поменял тем самым функцию из чужого модуля). Обёртка запускается вокруг обычной функции среды compile defun и запрещает компилировать одно определение в файле с таким-то именем. Поэтому данный файл можно загрузить только целиком (типо с соблюдением модульности). Можно было бы запретить и загрузку файла иначе, как в рамках asdf-системы. Можно было и для compile написать такой wrapper, но в моём случае было достаточно защиты от дурака, а не от взлома.

Причём всё средства защиты на уровне UNIX'овых «невидимых файлов». Если файл начинается с точки, то он невидимый, если символ в пакете доступен через два двоеточия, значит он внутренний.

Защита от изменений ортогональна к динамизму реализации. В SQL защита основана на выделении полномочий на объекты. Пользователь не может поменять процедуру, если ему не дали на это прав. И в CL нетрудно сделать версию интерпретатора с ограничением полномочий, это на lisper.ru обсуждали в своё время. Дополнительные ограничения, связанные с границами модулей, выразимы через ограничения отдельных объектов. Если они наложены раз и навсегда и их нельзя снять, то они вредны, а не полезны.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от monk

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

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

Если я ничего не путаю, то мы говорили о производительности труда именно программиста.

Тогда тем более. БД разрабатывают сначала в голове (или на бумаге), а не дописывают колонки по одной через ALTER TABLE.

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

Единственный пример функций как отдельных программ — qmail+daemontools. Но функциональности там минимум. Все остальные начинают от проектирования внутрипрограммного взаимодействия.

Понятие «базовых модулей» выглядит необоснованно придуманным.

Ты часто перекомпилируешь CL, ITERATE, ALEXANDRIA ?

Кроме расходов на пересборку, есть ещё и расходы на перезапуск.

Про kexec я уже упоминал. Для любителей «без перезапуска» в Racket есть функция enter!, но пользуются ей очень мало. Разве что надо работающую программу на новую весию обновить без потери состояния.

Возможность изменять на ходу одну функцию, переменную, определение класса независимо от места их определения - это основа процесса разработки в CL.

Вот это и длает нестабильной разработанную систему. Например, в своей библиотеке ты поправил (переопределил) iterate, чтобы он работал с символами независимо от пакета. А другой разработчик, рассчитывая на нормальное поведение iterate определет макросы table:for, xml:for для него. А кто-то пытается использовать ваши библиотеки совместно...

Это всё равно, что «возможность изменить на ходу любую область памяти — основа работы многих программ под MS DOS» и призывать отказаться от защищённого режима ОС.

Защита от изменений ортогональна к динамизму реализации.

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

Кстати, для себя разработчики SBCL таки сделали package-lock. Чтобы не переопределяли пакеты CL, SB-*.

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

Да ладно! До него были RedHat и SuSE. А ещё для домашнего пользователя Mandrake.

Это если самому настроить и посадить юзверя за узкий круг задач, чтоб если что - он бегал к тебе за помощью. До того тебе приходилось править настройки xfree86, ставить драйвер на видео (иногда на сетевую/звуковую, пересобирать ядро), руками ставить нужные пакеты, настраивать сеть и инет руками, т.к. е%ный drakconf вместо рабочего конфига генерил нечто совершенно иное, искать отдельно кодеки, чтоб слушать музыку и смотреть видео и т.д. и т.п. Добавить еще и общую сырость, скудость и глюкавость десктопного софта на линуксе (тогда так вообще писец был), и «обычный пользователь»™ вполне закономерно должен был считать эти дистрибутивы говном, а не десктопной ОС. Ну а для задротства - да, отличное время было, романтичное.

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

То есть у липса уже охеренная реклама изначально.

Так же как и UNIX. Он тоже был популярен.

Может это потому что материал сильно напоминает какашки

Макинтоши тоже никак не могли забороть MS DOS/Win95. Несмотря на то, что в школах/институтах чаще были макинтоши. Но простой пользователь смотрел, что может купить мак за две тысячи или с той же скоростью PC за тысячу и покупал PC.

Так и здесь. UNIX+C для своей работы требовал в десять раз меньше ресурсов, чем лисп-машина для выполнения тоё же задачи. А это стоимость. Все выбирают то, что дешевле.

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

ставить драйвер на видео ... drakconf вместо рабочего конфига генерил нечто совершенно иное ... искать отдельно кодеки, чтоб слушать музыку и смотреть видео

Что-то тебе совсем с Mandrake'ом не повезло. У меня настройка видео сводилась к выбору видеокарты и монитора. mpg123 был в дистрибутиве, mplayer тоже. К ним бывают кодеки?

Кстати, на Ubuntu на ноуте Dell внезапно оказалось, что mpg123 нет, а для гномопроигрывателя надо скачивать кодеки с интернета.

Добавить еще и общую сырость, скудость и глюкавость десктопного софта на линуксе

В Ubuntu кто-то написал совсем другой софт?

«обычный пользователь»™ вполне закономерно должен был считать эти дистрибутивы говном, а не десктопной ОС

К Ubuntu это относится в той же мере, что и к остальным дистрибутивам.

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

Что-то тебе совсем с Mandrake'ом не повезло. У меня настройка видео сводилась к выбору видеокарты и монитора

Скорее всего у тебя даже на то время была старая видеокарта и ты сидел за пятнашкой с каким-нибудь vesa-драйвером.

mpg123 был в дистрибутиве, mplayer тоже. К ним бывают кодеки?

Я про RedHat и, ЕМНИП, Suse. Мандрейк - да, клал на лицензии.

Кстати, на Ubuntu на ноуте Dell внезапно оказалось, что mpg123 нет

А накуя он там нужен по дефолту?

В Ubuntu кто-то написал совсем другой софт?

Нет, и там эта проблема тоже есть, но там хотя бы по дефолту ставят минимум в виде нормальных Firefox/LibreOffice etc., а их software center показывает пользователю популярность софта, его оценки и превьюшки, что явно лучше чем вывалить сходу кучу предустановленного говна или предложить искать нужное наугад в списке пакетов.

К Ubuntu это относится в той же мере, что и к остальным дистрибутивам.

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

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

БД разрабатывают сначала в голове (или на бумаге), а не дописывают колонки по одной через ALTER TABLE.

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

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

Наверное, ты не понял мою аналогию. Когда ты скомпилировал свою программу на Си и запустил её, тебе для этого не понадобилось пересобирать и перезапускать весь Линукс. Когда ты меняешь одну функцию в своей программе на Си, тебе надо пересобрать и перезапустить всю программу. В этот момент происходит большая потеря времени.

Ты часто перекомпилируешь CL, ITERATE, ALEXANDRIA ?

Нет, потому что это не мои пакеты/библиотеки. Отдельные функции в budden-tools я перекомпилирую регулярно, например, когда нахожу ошибку или когда добавляю новую функцию. Когда я делал iterate-keywords, я регулярно перекомпилировал iterate целиком и отдельные функции в нём.

Для любителей «без перезапуска» в Racket есть функция enter!, но пользуются ей очень мало.

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

Например, в своей библиотеке ты поправил (переопределил) iterate, чтобы он работал с символами независимо от пакета... А другой разработчик, рассчитывая на нормальное поведение iterate определет макросы table:for, xml:for для него. А кто-то пытается использовать ваши библиотеки совместно...

Это социальная проблема, она не связана с языком программирования.

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

Не вижу, как одно из другого следует.

стати, для себя разработчики SBCL таки сделали package-lock. Чтобы не переопределяли пакеты CL, SB-*.

Это защита только от случайного изменения, блокировка легко снимается. И она есть в других реализациях лиспа. Просто не стандартизирована. Я не утверждал, что защита не нужна. Я утверждал, что отсутствие защиты не может быть аргументом против динамизма. Возможность поменять то, что менять нельзя, зависит не столько от ЯП, сколько от доступа на запись к файлу программы. Например, в некоторых статических языках можно собрать программу с чужой библиотекой, получить доступ к private полям классов и компилятор не выдаст предупреждения. А можно и внедрять код в чужую программу, даже не имея исходников.

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

Например, по одной в месяц. Или по одной в квартал.

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

Когда ты скомпилировал свою программу на Си и запустил её, тебе для этого не понадобилось пересобирать и перезапускать весь Линукс.

Программа — аналог модуля, а не функции. Программы-функции — только в шелле. Ну и если у тебя программа работала, то всё равно тебе её прибётся перезапускать (потеря состяния будет). Единственное исключение — kexec. Глючное.

Наверное, раз не пользуются, что-то с ней не в порядке. В CL с подобными средствами полный порядок.

Вот полный аналог CL-ного SLIME: http://nongnu.org/geiser/geiser_4.html#To-eval-or-not-to-eval

Выполнение в текущем REPL.

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

Это социальная проблема, она не связана с языком программирования.

Почему же? Если все определения модуля в одном файле, то этот модуль можно покрыть юнит-тестами и гарантировать, что он будет работать в любом окружении. А вот как ты будешь делать юнит-тесты для любого нелокального изменения — неясно. Если в одном языке «социальная проблема» есть, а вдругом нет, то она связана с языком програмирования.

Иначе «социальная проблема» — это, например, возможность воткнуть разъём вверх ногами (причём он сгорает), провода без изоляции, стройка без ограждений — ведь если все будут акуратны и внимательны, то всё будет хорошо.

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

Статика/динамика и наличие/отсутствие нормальной модульности мало связаны. Согласен.

-----

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

Так задача — получить хорошую программу или чтобы программист насладился процессом? («детей я не люблю, но вот сам процесс...» (с) Ржевский)

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

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

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

alter procedure в сумме с транзакциями позволяет патчить код (модульно), не влияя на текущую работу пользователей: они увидят изменения вместе со стартом новой транзакции или коннекта. Даже это нужно в жизни. Представь, сколько будет стоить требование на запрет работы на время alter-а в системе продажи ж.д. билетов. Ведь alter table может занимать очень большое время, если, к примеру, ты накладываешь constraint на таблицу.

Можно вообще утверждать, что область применения статических систем ограничена теми случаями, когда

а) состояние стоит дёшево

б) возможные перерывы работы системы стоят дёшево

Это далеко не все системы. Самые важные дорогостоящие системы имеют дорогостоящее состояние и высокие требования к непрерывности работы. Потому-то весь программный мир крутится на динамических операционных системах и на динамическом sql, а монолитные «прошивки» используются только в микропроцессорной технике, где к этому вынуждают ограничения возможностей железа.

В тех случаях, когда сохранение состояния не необходимо, оно, как правило удобно. Отладка и разработка в каждый момент ведутся на острие иглы, а сама система может быть большой. Чем она больше, тем дороже сброс состояния в цикле разработки. image-based подход позволяет управлять этим, минимизируя потерю состояния. Конечно, если бага такова, что состояние стало слишком испорченным, можно закрыть образ и открыть его заново. Но те 95% случаев, когда это не нужно, дают гигантскую экономию времени и работы.

Программа — аналог модуля, а не функции.

Можно проводить разные аналогии. Например, ls или grep - это скорее аналог функции. Но ты можешь их заменять динамически в системе без пересборки линукса. Да, если ты нажал Ctrl-C в запущенном grep, то состояние grep пропадёт, но оно легковесно по сравнению с состоянием всего линукса.

А вот как ты будешь делать юнит-тесты для любого нелокального изменения — неясно.

Да, такая проблема есть. Но:

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

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

В-третьих, в системе может быть возможность видеть, какие патчи наложены и в этом случае ты можешь точно знать - пропатчен твой модуль или нет. Хотя в CL такой возможности нет, её можно добавить, перекрыв compile. В sql ты можешь сравнить реальный код с неким эталоном, записанным в файле, и тем самым удостовериться, что никто не залез грязными руками в твою базу и не сделал ненужный alter.

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

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

Статика/динамика и наличие/отсутствие нормальной модульности мало связаны. Согласен.

Вот и славно.

Вообще, спор у нас, кажется про разные цели языка.

Нет. Ты ясно высказался против image-based подхода, отсюда и спор. К документированию и тестированию это опять-таки почти ортогонально.

Так задача — получить хорошую программу или чтобы программист насладился процессом?

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

Если посмотреть на sql, то image-based подход там продвинулся гораздо дальше, чем в лиспе. В норме база Sql вообще не имеет исходных текстов в файлах - они хранятся в самой базе. Там же есть средства рефлексии, позволяющие извлечь исходник, узнать сигнатуру, построить граф вызовов, извлечь докстринги и т.п. И, невзирая на твоё мнение, sql очень и очень популярен. Что есть повод задуматься о том, правильна ли твоя точка зрения.

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

Поэтому я за Common Lisp и против Racket.

Racket такой же динамический, как и CL, зачем говорить о том, в чем не разбираешься? (enforce-module-constants #f) - и все что можно сделать в CL можно делать и в Racket.

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

Нельзя только изменять импортированные из других модулей объекты.

На самом деле можно.

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

Возможность изменять на ходу одну функцию, переменную, определение класса независимо от места их определения - это основа процесса разработки в CL.

Просто чтобы фигней не страдали больше:

> (compile-enforce-module-constants #f)
> (module m racket
    (provide x)
    (define x 0))
> (module m2 racket
    (require 'm)
    (provide d)
    (define (d)
      (displayln x)))
> (require 'm2)
> (d)
0
> (require racket/enter)
> (enter! 'm)
> (define x 1)
> (enter! #f)
> (d)
1
> 
все переопределяется прекрасно

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

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

Только это все ортогонально динамике и работе с образом.

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

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

И здесь тоже существенная разница восприятия мира. Считать, что «программа живая» сделует всегда или это особый случай. В CL считается, что нормальное состояние «программа живая», особый случай — устоявшаяся архитектура и API. В Racket — наоборот. Средства для работы с «живой программой» есть, но это особый случай.

alter procedure в сумме с транзакциями позволяет патчить код (модульно), не влияя на текущую работу пользователей

В CL у тебя нет транзакций. Если ты обновил полсотни функций, то пока они будут компилироваться/загружаться, система будет крайне нестабильна. В Racket enter! или dynamic-rerequire на модуль и то лучше — он атомарный (до полной перекомпиляции/загрузки модуля все функии используются старые).

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

Решаемы задачи и по переводу базы на другое железо и по переводу на другую СУБД. Если бы Alter блокировал базу и было критично время простоя, то просто было бы его сложнее сделать, но не невозможно.

Это далеко не все системы. Самые важные дорогостоящие системы имеют дорогостоящее состояние и высокие требования к непрерывности работы.

Тогда почему CL, а не Erlang? В Erlang'е я могу обновить запущенную функцию не прерывая её работы. Есть стандартная схема гарантии непрерывности работы (супервайзеры, assert-оподобная структура программы).

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

Поздразумеваешь, что пользователь — программист на CL: «И я рекомендую писать на Racket, если только конечными пользователями программы не являются исключительно программисты на Common Lisp.»

Ты ясно высказался против image-based подхода, отсюда и спор.

Да. Я считаю, что image-based подход поощряет отсутствие проектирования, юнит-тестов и нормальной документации (не docstring, а с описанием структуры программы и сценариев использования).

Если посмотреть на sql, то image-based подход там продвинулся гораздо дальше, чем в лиспе.

В sql нету состояния программы (кроме таблиц на диске). Для тестирования приходится использовать внешние средства (теоретически можно писать тесты в ALTER PROCEDURE, но я такого не видел). Документации на структуру БД внутри БД тоже видеть не приходилось.

SQL как правило идёт дополнительной технологией к существующему проекту. Поэтому для проектирования, тестирования, документирования используются механизмы основного проекта (Java, C#, Racket, ...). То же самое относится ко всем встраиваемым языкам.

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

В CL считается, что нормальное состояние «программа живая», особый >случай — устоявшаяся архитектура и API. В Racket — наоборот.

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

В CL у тебя нет транзакций.

Я говорил о преимуществах image-based подхода, а не только одного CL.

Если бы Alter блокировал базу и было критично время простоя, то просто было бы его сложнее сделать, но не невозможно.

Я на это уже отвечал.

В sql нету состояния программы (кроме таблиц на диске).

Что за двоемыслие? Какоё ещё «кроме»? Похоже, ты пытаешься подправить реальность, чтобы она не мешала твоим воззрениям. Дело твоё, но зачем тут моё присутствие?

Да. Я считаю, что image-based подход поощряет отсутствие проектирования, юнит-тестов и нормальной документации (не docstring, а с описанием структуры программы и сценариев использования).

Ну, считай дальше.

SQL как правило идёт дополнительной технологией к существующему проекту.

Опять пытаешься замести под коврик реальность, которая не укладывается в твои догмы. Попробуй обойтись без SQL, а когда получится - тогда и говори о его вспомогательности. Сейчас ты бросишься в слово NoSql, но вот тебе цитата http://docs.mongodb.org/manual/core/index-creation/

«By default, creating an index blocks all other operations on a database. ... For potentially long running index building operations, consider the background operation so that the MongoDB database remains available during the index building operation. »

Видишь, разработчики mongoDB тоже почему-то предпочли image-based подход.

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

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

Попробуй обойтись без SQL

Как раз для того, чтобы обходиться без SQL, и придумали ОРМ.

Что за двоемыслие? Какоё ещё «кроме»? Похоже, ты пытаешься подправить реальность, чтобы она не мешала твоим воззрениям. Дело твоё, но зачем тут моё присутствие?

Ну так и вне image-based подхода у тебя нет состояния КРОМЕ ИСХОДНИКОВ НА ДИСКЕ, лол. Что за двоемыслие?

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

Возможно, но они неотличимы от анонимусов - троллей. Как-то меня славно потроллили и с тех пор я зарёкся. Именно в рамках споров.

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

Создай топик, анонимусы подтянутся

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

Я бы сказал, что юнит-тесты потому можно не считать «запуском программы»

Речь о тестах вообще, а не исключительно о юнит-тестах.

давайте всё же согласимся, что статическая типизация и динамическая реализация друг другу не противоречат.

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

во всех SQL-серверах есть возможность подать запрос с консоли, т.е. eval, а также alter table и alter procedure, т.е., SQL реализован динамически

Это не доказывает ровно ничего. Первая реализация SQL (описанная у Дейта SQL/DS, кажется) использовала статическую компиляцию запросов, а при ALTER TABLE просто перекомпилировала их. Насчёт eval - никаких проблем скомпилировать строку нет, а в статически типизированных языках реализован REPL. Короче, если компилятор или чекер являются, упрощенно говоря, библиотечным функциями, ты можешь проделывать со статически типизированным кодом все те же трюки, что и с динамически типизированным. По крайней мере, в теории - из практических реализаций пока только REPL.

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

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

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Ха. Да эта ваша динамическая типизация - частный случай статической.

покажи мне сигнатуру рекурсивного списка на хаскеле (с неизвестным на этапе компиляции уровнем вложенности)

x4DA ★★★★★
()
Последнее исправление: x4DA (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Да эта ваша динамическая типизация - частный случай статической. Если серьёзно - я согласен, не противоречат.

Я не уверен, что ты правильно прочитал или понял слова «динамическая _реализация_». Динамической реализацией я называю возможность горячей замены кода, в том числе, с изменением сигнатуры функции или класса. Которая в значительной степени ортогональна к тому, динамической или статической является типизация.

Так что твой следующий абзац у меня не вызывает возражений.

Пока у меня не отнимают тип «что угодно», дающий возможость делать типизацию динамической, а также функцию type-of, которая принимает в качестве аргумента «что угодно», мне предпочтительнее статическая типизация.

Динамическая типизация останется экзотическим режимом работы, изредка полезным, или просто отомрет.

Ну, поживём - увидим.

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

Хотя, если подумать, то всё не так просто. Один и тот же код может быть типизирован безконечным числом способов. Например, 1 - это целое, натуральное, рациональное, комплексное число, а заодно ещё и одномерный массив длиной 1. Если система типов - плохая, то она может сильно испортить жизнь, что зачастую можно наблюдать в С++. Это больше зависит от программиста, чем от языка, но если системе типов в языке придаётся слишком большая роль и при этом система типов не слишком хороша, то можно ожидать проблем.

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

Ну и ну. Не ожидал такого открытия.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

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

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

Типы определяют как layout, так и возможные операции над массивом, так в hasell 1 будет иметь только Constraint 'Num', (если нету ограничений фиксирующих его). В этом единица может отказаться любым тип имеющим соотвествующий инстанс, т.к. как ты и хочешь.

Я вообще не сильно понимаю, о какой динамике идёт речь, когда динамика в ЯП это лишь жизнь в фиксированном value universe, типа:

data Lispy = Cons Lispy Lispy
           | S String
           | N Int
           | D Double
           | V Lispy
           | F (Lispy -> Lispy) -- ^ see $calling_convernsion
qnikst ★★★★★
()
Ответ на: комментарий от qnikst

ну например как выразить в такую функцию в типах:

flatten: [[[...[a]...]]] -> [a]

на вход поступает список с nested списками, на выход - просто список из всех конечных несписковых элементов.

x4DA ★★★★★
()
Последнее исправление: x4DA (всего исправлений: 1)
Ответ на: комментарий от qnikst

Вместо фолдабл ToList можно в принципе, это не важно. Или нужно не позволять другие типы кроме вложенных списков?

Ну и я предположил, что вдоженность разная иначе проще.

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)
Ответ на: комментарий от qnikst

Я вообще не сильно понимаю

Видимо ты просто лисп не знаешь. В CL есть классы и структуры (с наследованием), а также массивы, все они могут иметь типизированные поля (элементы). Можно применять тип «тип1 или тип2», можно заводить свои типы, в т.ч. параметрические (см. deftype). Для чисел есть интервальный тип (целое число от 0 до 28). Можно определить тип, представляющий множество всех лисповых значений, удовлетворяющих произвольному предикату (http://www.lispworks.com/documentation/HyperSpec/Body/t_satisf.htm#satisfies). Можно ограничивать типы, принимаемые и возвращаемые функцией. Вроде всё перечислил, хотя может быть, что-то и упустил.

Чисто в динамике можно проверить

(typep x '(or integer float))

, получим истину, если x - целое или плавающее число.

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

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

А забыл ещё List Lispy Lispy: Тогда берём и пишем: List (S «predicate-obj») (L (F somefunction) (Cons (S «object») ...)

В общем для всего перечисленного выше - хватит :)

qnikst ★★★★★
()
Ответ на: комментарий от qnikst
% echo "data F a = forall x . Foldable x => F (x a)
        flatten' = [F a] -> [a]" > tst.hs
% ghc -XExistentialQuantification tst.hs 
[1 of 1] Compiling Main             ( tst.hs, tst.o )

tst.hs:3:18: parse error on input `->'

я хочу функцию которой на вход я закидываю список, например [1, 2, [2, 8, 9, [[[[[5]]]]] [3, 14]]], а на выход получаю сплюснутый список [1,2,2,8,9,5,3,14]

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