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)
Ответ на: комментарий от monk

Ан нет, оказалось, что эта операция копируют весь хвост строки.

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

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

А вот так работает?

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

> a
(1 2 3 4 5 7 9 15)
> filtered
(1 3 5 7 9 11)

Если мне будет нужно отфильтровать новый массив я вызову array_filter еще раз, явно. Или не вызову, если будет не нужно. Кто тут потоком управления командует, программист или яп?

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

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

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

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

На базе cons это работает. На базе массивов попробуй напиши filter, чтобы у двух массивов был общий хвост.

monk ★★★★★
()
Ответ на: комментарий от monk
function cons(car, cdr) { return [car, cdr] }
function car(cons) { return cons[0] }
function cdr(cons) { return cons[1] }

Идея понятна? Надо делать список, да, просто массив так не может. Но зачем нужен cons как базовая структура когда есть другие?

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

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

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

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

Списки у вас не начнут последовательно лежать,

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

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

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

Для чего же нужны cons?

Связные списки в лишпах используются для представления кода. Код на лишпе — это не какой-то текст, а натурально структуры данных, используемые самим языком.

Примерно как если бы код на JS представлял собой надмножество JSON(L). Что-то вроде

[
  [let a 12]
  [let b {
    c: 25
  }]
  [+ a 75 b.c]
] 

(Допустим, что в этом JS есть литералы для символов и в них допускаются знаки препинания.)

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

Но зачем нужен cons как базовая структура когда есть другие?

Потому что есть пачка функций, которые умеют работать со списками из cons. А изначально cons и symbol нужны были для того, чтобы удобно писать макросы.

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

Есть PicoLisp без массивов. Достаточно хорошо работает.

вот он, морковкин, сделан на как раз на юнитах размером в конс-пару. там все в таких юнитах расположено. и ни в чем другом.

https://software-lab.de/doc/ref.html#vm

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

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

Для этого есть ООП. Что будет если в Си по указателю пытаться обращаться за пределы массива? Проблема не уникальная для ЛИСПа.

Я проводил не бенчмарк сложения и циклов, а бенчмарк cons

И где в твоём коде cons?

Если обрабатывтаь их на cons, то это будет ужасно медленно по сравнению с JSON

Почему это? Нормальная будет скорость, опять же, если ты не жопой пишешь и используешь деревья (а они строятся из cons).

Ну а вообще в ЛИСПе есть и массивы и хэши. ЯННП, в чём претензия-то?

no-such-file ★★★★★
()
Ответ на: комментарий от MOPKOBKA

выбирай эта {head, tail} или может такую [head, tail]? Какой смысл в паре как отдельной структуре

Смысл в том, что специализированные алгоритмы работают быстрее общих и данные меньше места занимают.

no-such-file ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Даже если мы будем вставлять элементы в случайные позиции

А зачем их вставлять в случайные позиции? Специально чтобы медленно работало?

Но к слову о полезности односвязных списков. Вот ты выбрал некую простую задачу ткнув пальцем в небо. И как бы случайно, но для этой задачи односвязные списки прекрасно подходят, т.к. вставлять можно в начало. Совпадение? Не думаю. А то что ты не осилил это адекватно реализовать, то это уже твои проблемы, а не cons-ов.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)

И что этот твой бенчмарк должен бенчамаркать? Бред какой-то написан.

Не надо гнать пурги на лисп. Ты его сначала освой!

И тут скорее проблема не в лиспе, не в ФП, где односвязный список - основа основ, а дело в непонимании нотации O-большое.

anonymous
()

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

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

buddhist ★★★★★
()
Ответ на: комментарий от no-such-file

Тормоза.

Python в 10 раз медленнее С на бенчмарке из ОП-поста. Во сколько раз при этом он быстрее Lisp, подсчитаешь сам.

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

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

Для этого есть ООП. Что будет если в Си по указателю пытаться обращаться за пределы массива? Проблема не уникальная для ЛИСПа.

В С принято передавать размер массива, если не числом, то хотя бы размером других аргументов как в strcpy. А в Lisp я не видел функций, в которые можно передать текущий конец, и получить его после модификации. Для простейших задач, придется откинуть весь код чужой.

И где в твоём коде cons?

Нету, в том и суть.

Почему это?

Потому что cons медленные? Я это уже наглядно продемонстрировал. Что ты там собрался «нормально писать», если почти все требуемые операции требуют прохода по cons?

Ну а вообще в ЛИСПе есть и массивы и хэши. ЯННП, в чём претензия-то?

1) Почему бы не уйти от cons подальше, как сделано в Clojure, 2) Почему они такие неоптимизированные, зачем отдельно массивы когда можно было бы под капотом превращать cons в массивы?

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

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

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

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

И кстати, автор такой же «бенчмарк» легко может получить в java и c#, если использовать стандартные строки.

Предъявы от него тогда справедливы и в сторону жабы и дотнета тогда тоже. Абсурд, какой он есть.

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

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

прошу написать мне список ПО реализованного на PicoLisp.

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

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

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

Потому в лиспе «список» ничего не знает о свой длине и своем последнем элементе.

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

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

В С++ нету pair как отдельного элемента на уровне языка, он как раз построен из «других».

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

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

не будем отклоняться от темы.

Вот твой пример на массивах, единый хвост:

function cons(car, cdr) { return [car, cdr] }
function car(cons) { return cons[0] }
function cdr(cons) { return cons[1] }
function setcar(cons, v) { return cons[0] = v; }

const l3 = cons('Лондон', null)
const l1 = cons('Москва', l3);
const l2 = cons('Париж', l3);
setcar(l3, 'Пекин');

// 1: Москва, Пекин
// 2: Париж, Пекин
console.log(l1, l2); 

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

И кстати, автор такой же «бенчмарк» легко может получить в java и c#, если использовать стандартные строки.

Если бы Java или C# использовали строки в качестве строительного блока, как TCL, и имели бы такие проблемы с ними, я бы тоже возмущался.

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

[car, cdr] - это физически что?

Массив. На С выражается так же как и мой массив:

struct JS_Array {
  int length;
  struct JS_Atom *atoms[];
};

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

Я сегодня первый раз в жизни, осознанно, запустил Common Lisp код

Мои поздравления :)

Правильнее делать бенчмарк всё же используя внутреннюю функцию time:

➤ sbcl                                                                                                                                     
This is SBCL 2.5.3, an implementation of ANSI Common Lisp.
...
See the CREDITS and COPYING files in the distribution for more information.
* (load "e.lisp")
T
* (time (main))
4999950000
Evaluation took:
  24.401 seconds of real time
  24.421451 seconds of total run time (21.707460 user, 2.713991 system)
  [ Real times consist of 4.228 seconds GC time, and 20.173 seconds non-GC time. ]
  [ Run times consist of 4.230 seconds GC time, and 20.192 seconds non-GC time. ]
  100.08% CPU
  56,220,594,890 processor cycles
  80,000,782,912 bytes consed
  
NIL

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

используя внутреннюю функцию time:

Ну это для всего так, ага

Я ещё «много думал» почему оно быстрее чем C отрабатывает, а только потом понял что надо (main) в коне всё же вызвать :D

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Тогда на PicoLisp коммерческих всего две фигни:

Isar stands for Image Station for Retouch (1988-1993 год, MacOS)

Camballage was a CAD system to control a laser cutter under X11/SCO Unix (1991 год).

Некоммерческие в основном короткие скрипты.

И он входит в состав Debian, так что не совсем студенческая поделка.

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

В JS массивы не копируются, как и в Lisp списки передаются по ссылке.

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

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

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

то есть прокламируя тезис, что конс-пары неэффективны… ты нарисовал их ручками как массив из двух указателей (так же как в лиспе) и …что этим доказываешь-то?

Что конс-пары неэффективны для многих задач, и не годятся в качестве строительного блока. А JS массивы эффективны, пример из ОП-поста там выполнится очень быстро.

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

Что конс-пары неэффективны для многих задач, и не годятся в качестве строительного блока

Это ваши руки неэффективны для многих задач, потому как растут ниже уровня пояса! Я Лиспа толком не знаю, но переписал вот так:

(proclaim '(optimize speed))

(defun main ()
  (declare (optimize (speed 3)))
  (let ((n 99999) (l `()))
    (labels ((local-sum-tco (a items) 
               (if (null items)
                    a
                    (local-sum-tco  (+ a (car items)) (cdr items)))))
        (dotimes (p (1+ n))
          (setf l (cons p l)))
        (format t "~d~%" (local-sum-tco 0 l)))))

Получил сравнимое для n=99999 время, что и для C:

* (time (main))
4999950000
Evaluation took:
  0.000 seconds of real time
  0.000786 seconds of total run time (0.000778 user, 0.000008 system)
  100.00% CPU
  1,807,206 processor cycles
  1,604,848 bytes consed

Вариант на С:

➤ gcc -O3 test.c -o testC && time ./testC                                                                                                     
4999950000
./testC  0.01s user 0.00s system 3% cpu 0.300 total

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

RTFM! Advanced Techniques for Common Lisp - Paul Graham 1993 - тридцать лет назад все расписали как и что нужно делать, а до сих пор неучи вылазят. Идиоматичное решение из второй главы:

(defun triangle (n)
  (labels ((tri (c n)
             (declare (type fixnum n c))
             (if (zerop n)
                 c
                 (tri (the fixnum (+ n c))
                      (the fixnum (- n 1))))))
    (tri 0 n)))
* (time (triangle 99999))
Evaluation took:
  0.000 seconds of real time
  0.000082 seconds of total run time (0.000081 user, 0.000001 system)
  100.00% CPU
  185,518 processor cycles
  0 bytes consed

P.S. MBP-2019 2,3 GHz 8-Core Intel Core i9

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

Суть в измерении работы с cons, у тебя cons нету. Ну прям типичный комментарий на ЛОРе, ничего не понял, написал в итоге совсем не по теме, и считает себя самым умным.

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

написать мне список ПО реализованного на PicoLisp

А можно мне ещё, любого ПО реализованного на любом лиспе, которое сейчас активно используется? Я только emacs знаю. И заодно на forth/factor и прочих стековых, которые как лиспы, легко сделать с нуля. Прям есть серьёзный большой софт?

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

Нету, в том и суть

Я это уже наглядно продемонстрировал

Что именно, если нету?

Что ты там собрался «нормально писать», если почти все требуемые операции требуют прохода по cons?

Убрать 50000 проходов и cons на итерацию (в среднем), например. Ты просто не умеешь программировать.

Почему они такие неоптимизированные

Они оптимизированные, это у тебя руки из жопы.

зачем отдельно массивы

Затем что массивы это совсем другая структура данных.

Даже со вставкой в начало код будет ужаснейше тормозить

Не будет. Я показал пример.

no-such-file ★★★★★
()
Ответ на: комментарий от MOPKOBKA

у тебя cons нету

Это у тебя нету. А у него-то как раз есть (setf l (cons p l)).

PS: какой-то троллинг тупостью, очень жирно уважаемый овощ.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)