LINUX.ORG.RU

Lisp: Где применимы cons?

 cons,


1

6

Для начала небольшой «бенчмарк», С без всех оптимизаций в 7406000 раз быстрее.

(defun main ()
  (declare (optimize (speed 3)))
  (let ((n 99999) (l '()) (sum 0))
    (loop for i from 0 to n
	  do (setq l (append l (list i))))
    (setq sum 0)
    (loop for i from 0 to n
	  do (setq sum (+ sum (nth i l))))
    (format t "~d~%" sum)))
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
	long n = 99999, *l = NULL, count = 0, sum = 0;
	for (long i = 0; i <= n; i++) {
		l = realloc(l, (count + 1) * sizeof(long)); 
		l[count++] = i;
	}
	for (long i = n; i >= 0; i--) sum += l[i];
	printf("%ld\n", sum);
}

Для чего же нужны cons? В качестве универсального строительного блока, я считаю это одна из самых худших структур. Все ее преимущества заканчиваются на быстром добавлении в начало. Добавление в конец уже нежелательно, разрез в произвольном месте тоже, так как нету даже быстрого доступа к случайному элементу. Она медленная и неудобная, можно придумать кучу более быстрых и удобных структур. Даже JS на световые годы опережает Lisp со своим JSON, и его частое использование лишь подтверждает его удобство.

Так почему же cons из языка-ассемблера IPL 1956 года считается важным? Да, это неплохая структура для AST, если ваша машина имеет 16 кб памяти, но она распространилась по языку слишком широко.

★★★★★

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

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

т/е(ща в экспозиции от ims codasyl и obj rel) теже транзакции не нужны(не обязательны) при отднопотоке - у Грея - причина победы транзакций в индустрии - необходимость мульти-доступа без непреемлемой деградации производительности

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

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

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

Отладчик это не редактор. Отладчик реально влияет на путь мышления. И поощряет исправления в точке, где проявилась ошибка, а не в точке, где появилась причина ошибки.

REPL, кстати, тоже влияет, но в положительную сторону: если есть REPL, программа будет разбита на короткие функции. Если нет REPL, то скорее, всего на крупные объекты со сложным внутренним состоянием (паттерн Information Expert и подобные).

Ну так это типичная работа программистом.

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

но как общий совет это просто смешно

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

А нельзя просто переопределить функцию? Это же динамический язык.

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

Это как вообще? Там безостановочный одношаговый режим?

Я имел в виду, после того, как произошла ошибка. Если в Common Lisp какая-то функция ошибочная, то можно её переопределить прямо в отладчике и тут же запустить выполнение дальше.

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

Отладчик это не редактор. Отладчик реально влияет на путь мышления.

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

Это общий совет того же уровня, что ходить надо не опираясь на стены.

Если уж подбирать аналогию, то есть очень хорошая. На всяких опасных работах, на высоте например, есть те кто говорят что без страховки безопаснее, так как относишься к безопасности более серьезно. Вот это оно. Зачем отладчик, тесты (твою позицию по тестам я понял, она другая), CI/CD, предупреждения компилятора, IDE, если они дают ложное чувство безопасности. Зачем страховка, каска, защитные очки, надо просто быть осторожнее.

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

Зачем отладчик, тесты (твою позицию по тестам я понял, она другая), CI/CD, предупреждения компилятора, IDE, если они дают ложное чувство безопасности.

Да нет же.

Попробую объяснить подробней.

Предположим есть примерно одинаковая достаточно сложная программа на 1С, Common Lisp, Racket. Обнаруживаю, что если нажать кнопку 1, потом кнопку 3, потом кнопку 5, потом кнопку 7, то выскакивает ошибка деления на 0. Каждая кнопка выполняет какие-то действия около 10 минут.

Дальше в разных языках я буду действовать по разному.

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

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

В Common Lisp аналогично 1С, но тестировать новую функцию буду прямо в отладчике. Поэтому тратить 10 минут на формирование тестовых данных не буду, поэтому исправление примерно аналогично 1С, но без нового описания теста для этой ситуации. Всё проверено в отладчике и потом один раз после перезапуска.

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

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

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

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

Дополнение. На https://forth-standard.org/ для каждого слова есть набор тестов, я их читал что бы лучше понимать что слово делает, это хорошее дополнение к обычному тексту и примерам использования, потому что там часто показаны и неочевидные случаи, которые бывают полезны.

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

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

С отладчиком исправление ошибки 2 минуты, написание теста 10 минут. Кто будет писать тест?

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

Так в Common Lisp это не сложно. В смысле, или всё работает и ничего не надо оббегать или есть ошибка, но она правится за пару минут (если тесты не писать).

На https://forth-standard.org/ для каждого слова есть набор тестов, я их читал что бы лучше понимать что слово делает

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

А в Common Lisp «тестами» являются только примеры использования библиотеки целиком из документации.

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

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

Так в Common Lisp это не сложно.

Если «программа» занимает 1 000 строк то возможно.

На форте ещё шаг дальше от Racket

Но в Forth нету такого отношения к отладчикам, Чарльз Мур наоборот отмечает что вот смотрите, отладчик компактно уместился в среду.

Иначе при отладке следующего слова заколебёшься.

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

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

А в Common Lisp «тестами» являются только примеры использования библиотеки целиком из документации.

Там и примеры есть, и тесты одновременно. Это все же разные вещи.

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

Отладчик это не редактор. Отладчик реально влияет на путь мышления. И поощряет исправления в точке, где проявилась ошибка, а не в точке, где появилась причина ошибки.

Как то это спорно очень. :)

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

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

Поэтому, по мне, отладчик особо ни к чему не подталкивает. :)

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

Если «программа» занимает 1 000 строк то возможно.

Ошибка всегда в маленьком подмножестве программы.

Но в Forth нету такого отношения к отладчикам, Чарльз Мур наоборот отмечает что вот смотрите, отладчик компактно уместился в среду.

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

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

Так тест слова и есть обычно сравнение состояния стека с ожидаемым. Иногда не только стека.

Да нет такой проблемы, если стек испорчен, очень быстро произойдет падение

С чего это? Падение произойдёт или если он исчерпается или если где-то не пройдёт проверка на тип данных (а она вроде мало где там есть).

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

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

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

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

Поэтому, по мне, отладчик особо ни к чему не подталкивает. :)

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

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

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

Такой способ разработки есть не только в лиспе и он никак не связан с отладчиком. :)

Причем ты расписал не самый «жирный» кейс. Я наблюдал, когда разработка идет на C++, дев что-то исправляет, отправляет пул реквест, его смотрят и подтверждают, запускается сборка проекта, через 20 минут, готовый бинарник вываливается тестерам, они проверяют это место и пишут что ошибка не исправлена. И итерация повторяется. :)

Вот лучше бы этот дев отладчиком бы пользовался. :)

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

Причем ты расписал не самый «жирный» кейс. Я наблюдал, когда разработка идет на C++, дев что-то исправляет, отправляет пул реквест, его смотрят и подтверждают, запускается сборка проекта, через 20 минут, готовый бинарник вываливается тестерам, они проверяют это место и пишут что ошибка не исправлена. И итерация повторяется. :)

Вот хороший отладчик позволяет эти итерации сократить до десятков секунд. И считать, что это нормальный процесс исправления.

Вот лучше бы этот дев отладчиком бы пользовался. :)

Лучше бы этому деву тим лид настучал в бубен за пул реквест без написания и прогона теста.

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

А в форте есть отладчик?

Конечно. Можно и выполнять команды, и восстанавливать старые состояния, и конечно же видеть переменные.

С чего это? Падение произойдёт или если он исчерпается или если где-то не пройдёт проверка на тип данных (а она вроде мало где там есть).

Там нету типов, падение произойдет при записи в память, просто собьется адрес очень быстро. Любое обращение к переменной это или чтение памяти в стек, или запись из стека. Код вида «1 x !» не просто записывает значение в переменную, он записывает значение по адресу, которое помещает в стек «x».

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

Вот хороший отладчик позволяет эти итерации сократить до десятков секунд. И считать, что это нормальный процесс исправления.

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

Первично отношение самого дева к разработке, а наличие хорошего отладчика вторично.

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

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

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

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

В PHP если вылетает исключение (а других ошибок типа segfault там не будет на типичном проекте), то ты сразу попадаешь в отладчик и видишь что откуда пришло, какие аргументы были у функций, подсвечены ветки if в зависимости от исполнения, можно выполнять произвольный код. PhpStorm изначально настроен на break on exception.

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

Трудно отладчик «пропустить».

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

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

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

Заметил нечестно получается с моей стороны, я осуждаю плохой отладчик в бесплатном SBCL, а для Forth привожу в пример проприетарные реализации. А как писал den73, в проприетарных реализациях CL тоже есть нормальные отладчики.

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

Разве возможности вводить машинный код недостаточно?

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

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

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

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

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

printf же всегда было средством последней надежды :)

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

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

printf же всегда было средством последней надежды :)

принты, это когда последняя надежда сменилась отчаянием.

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

бага не может быть только в релизной сборке. она есть и в дебажной. просто вы ее не видите.

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

Вот хороший отладчик позволяет эти итерации сократить до десятков секунд. И считать, что это нормальный процесс исправления.

Лучший отладчик это интепретатор, deal with it :)

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

Дифирицальная машины Стерлинга Гибсона парапанк

если серьёзно то мемуары Вил

Морис Уилкс, Великобритания (Maurice V. Wilkes; род. 26.06.1913)

начиная с http://rkka21.ru/turing_award.htm 1967 о предыдущем:

http://rkka21.ru/turing_award.htm#:~:text=Компьютеры%20прежде%20и%20теперь

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

Код вида «1 x !» не просто записывает значение в переменную, он записывает значение по адресу, которое помещает в стек «x».

Так этот код и не упадёт, если стек испорчен, он же не зависит от стека.

Я имел в виду, что, условно, есть

: dist (x y -- x^2 + y^2)
  sqr swap sqr + ;

А sqr определили с ошибкой. Вместо dup * написали dup dup *. Падения не будет (лишний же элемент), но dist будет писать не то.

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

бага не может быть только в релизной сборке. она есть и в дебажной. просто вы ее не видите.

Если у релизной и дебажной разные флаги компилятора, то легко. Особенно в Си/Си++. Достаточно единственного UB в собираемой программе.

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

:)

https://dl.acm.org/doi/pdf/10.1145/1500774.1500876

куча кусочков навалена в http://bitsavers.informatik.uni-stuttgart.de/

----------------- ща откурил от абака до домпьютера (1 том из 2ух)

ваще на английском есть буквально отчёты на сайте мухея БэбиДжа

а не - института https://cse.umn.edu/cbi https://en.wikipedia.org/wiki/IT_History_Society#Charles_Babbage_Institute

ну и классика https://computerhistory.org/

корма много

так же https://www.youtube.com/Computerphile субтитры их прохладных историй чит ать

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

Если у релизной и дебажной разные флаги компилятора, то легко.

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

конечно, это сделать легко. но этого делать не надо.

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

Так этот код и не упадёт, если стек испорчен, он же не зависит от стека.

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

Я имел в виду, что, условно, есть ... sqr

Если перед этим лежал адрес, и нужно будет теперь обратно записать значения, то уже падение. Ну и просто если в отладчике посмотреть место после выполнения dist, будет видно что значений на стеке больше чем нужно. Мотаешь назад и быстро видишь где сбой, но прежде стоило проверить слово в REPL конечно.

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

Ну и просто если в отладчике посмотреть место после выполнения dist

Вот это я и имел в виду. Если для лиспов достаточно проверки контракта на входе в функцию, то для форта надо или обязательно писать тесты или в отладчике смотреть состояние стека. Причём именно стека. Простая проверка типа «5 sqr .», увидел 25 недостаточна.

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

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

Если для лиспов достаточно проверки контракта на входе в функцию

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

Кстати о красоте лиспа и бесконечных списках... Как выглядит функциональная имплементация функции length?

Простая проверка типа «5 sqr .», увидел 25 недостаточна.

Достаточно «5 sqr», главное включить отображение стека после каждой введенной команды REPL.

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

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

Это же лисп, а не хаскель. Бесконечных списков не бывает.

Как выглядит функциональная имплементация функции length?

Как всегда, хвостовая рекурсия с аккумулятором результата:

(defun my-length (list)
  (labels ((recurse (list result)
             (if list (recurse (cdr list) (1+ result)) result)))
    (recurse list 0)))

или можно красиво написать

(defun my-length (list) 
  (reduce #'+ list :key (constantly 1)))

если считать, что уже есть свёртка.

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

Ну значит это зацикленные списки а не бесконечные, на них твоя функция вылетает. Ошибку бы какую нибудь... Без сторонних функций типа fold.

(defun my-length (list)
  (labels ((recurse (list result)
             (if list (recurse (cdr list) (1+ result)) result)))
    (recurse list 0)))
    
(defvar lst '(1 2 3 nil))
(setf (cdr (cdr (cdr lst))) lst)

(my-length lst) ;; stack overflow

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

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

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

Дополнение. На https://forth-standard.org/ для каждого слова есть набор тестов, я их читал что бы лучше понимать что слово делает, это хорошее дополнение к обычному тексту и примерам использования, потому что там часто показаны и неочевидные случаи, которые бывают полезны.

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

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

еще лучше писать их в литературно-грамотном стиле.

словно литературно-грамотный метапрог с конпелируемыми подфункциями

anonymous
()