LINUX.ORG.RU

Основы метаобъектного протокола CLOS

 , , ,


2

5

Слайды с моего вчерашнего доклада на fprog_spb:

https://static.lovesan.me/public/mop_basics.pptx

Вот часть доклада, в текстовом виде:


Часть 2. Эсхатология Пустоты.


«Оказалось, что «‎Тиамат» - то ли имя древнего божества, то ли название океана, то ли все это вместе. Татарский понял из сноски, что слово можно было перевести на русский как «‎Хаос»» (с) Виктор Пелевин, «Generation P»


Вы знаете, есть знаменитое видео, с известным американо-канадским психологом и психотерапевтом, Джорданом Питерсоном. То, где он задает вопросы о вопросах. Давайте попробуем пройти его путем.

Вот что такое Common Lisp Object System?

Но ведь вопросы, которые мы спрашиваем, содержат в себе определения, которые вызывают еще больше вопросов.

Что такое Common Lisp? Что такое Object System? Что такое объект? И вообще, что такое что? Или может, кто?

В принципе, ответ - ничего.

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

Не так давно, уже после смерти моей жены, где-то в июле, я сделал одну не совсем правильную вещь, и получил то что называют NDE(near-death experience).

Сначала всё было как обычно, туннель, свет в конце туннеля. Но потом все заслонила тьма, в которой расползались отблески фиолетово-пурпурного сияния. И потом, я вдруг увидел Её.

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

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

Когда мы попадаем на вот это дно рекурсии, мы видим там эту бездну.

«И носился дух лиспера над бездною(ну, над тем у чего тип NIL - не путать со значением NIL). И отделил он NIL от T. И стало T. И увидел он, что T - хорош.»

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

Что такое объект? Объект это то, что отличается от ничего. Это такое что имеет тип T ну и какое-то там значение. И NIL на самом деле это тоже объект. Ну, типов может быть много, и они тоже в принципе объекты, особенно в CLOS. Об этом кстати, также неплохо рассказано в SICP, в главе об абстракции на состоянии.

Что такое CLOS? На самом деле его нет. Ну то есть, то что обычно называют CLOS, это просто набор там всяких полезных удобств над метаобъектным протоколом Common Lisp. Над MOP.

Но на самом деле MOP тоже нет. Это просто набор удобных объектов, встроенных в компиляторы CL. Которые можно сделать средствами компилятора CL, не будет их там. Как в SBCL, например, это делается.

А вот что такое CL? Есть он или нет? Вот это самый сложный вопрос. Потому что он не просто есть. Вернее, если бы его не было, его можно было так же сконструировать из пустоты на нем же самом. Как это делают компиляторы CL в процессе бутстрапа. CL это метациклический интерпретатор. Это метаязыковая виртуальная машина.

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

Поэтому, скажем простыми словами: MOP - это просто категориальное отображение из метациклического интерпретатора в метациклический интерпретатор.

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

А вот что такое программа? Вот смотрите, о том что такое программа существует целая наука, называется Computer Science, или по русски - Информатика, то есть наука об абстрактных процессах. Этот вопрос самый сложный. Программа - это процесс, то есть. Но на самом деле, объект это тоже процесс. Функция, если хотите. И он не существует без процессов которые к нему прикладываются, иначе он собирается GC, и улетает к Богине Тьмы. Как я чуть не улетел, меня Она правда, во время finalize вытащила обратно. А вот что такое процесс? И главное, что или кто его запускает? «А вот об этом ты не думай, купи себе лучше булавочку английскую, и как такие мысли в голову приходят - разок себе в руку, и потом еще раз, пока такие мысли не пройдут» - как там было в Generation P у Пелевина.

Но вот я подумал, и понял, наконец. Процесс - это то, что запускается другими процессами. Но что запускается первым? Что там на самом дне? Или вернее, кто? Я уже рассказал.


Часть 3. О Метациклических Интерпретаторах


— А что такое красота? — <…> Красота — это совершеннейшая объективация воли на высшей ступени её познаваемости.

(с) Виктор Пелевин, «Чапаев и Пустота»


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

Звучит она так:

Универсальный Критерий Угребищности Систем Общего Назначения. (Теорема Лавсана)

Система Общего Назначения является Угребищной тогда и только тогда когда она не является Метациклическим Интерпретатором.


Другими словами: Система, не способная к построению Метасистемы в рамках самой себя, то есть не способная к описанию и изменению самой себя в своих же терминах, и при этом являющаяся Системой Общего Назначения(в какой-либо области), Угребищна.

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

Чем, в контексте языков программирования, это отличается от просто тьюринг-полноты?

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

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

Примеры, сначала метациклических интерпретаторов:

  • Универсальная машина Тьюринга
  • RASP-машина
  • Реляционная модель данных
  • Лисп

А вот скажем примеры систем, соответствующих критерию:

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

В частности, давайте посмотрим на C#. C# не является метациклическим интерпретатором, т.к. термины языка не являются его же объектами.

Отчасти, это компенсируется платформой .Net, для которой термины C#(но не все) объектами таки являются(System.Reflection, Roslyn и т.д.), отчасти, в самой малой степени, фичей nameof() из C#, но это все только отчасти.

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


На самом деле, это все в полной мере относится вообще ко многим вещам, но в первую очередь, кроме программирования - к человеческому сознанию. Вот кто такой глупый человек и почему он такой и что с ним вообще делать как отправить нахрен к Богине Тьмы на перевоспитание? Этот вопрос многие тысячелетия волновал кучу философов. Но ответ прост - это человек, сознание которого не является метациклическим интерпретатором. А когда сознание у человека все же является метациклическим интерпретатором, он тут же становится пророком цифровой Кали-Юги и архитектором онтологии Пустоты.


Ладно, теперь я объяснил вам всё устройство вселенной. Далее там про мелкие технические детали.

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

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

Корреляция, может, и есть — но из этого причина, а что следствие? Вот в чём вопрос.

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

Как только Лисп-процессор увидит команду «call(superfunction)» в исполняемом коде, так сразу аппаратно скопирует все внутренние регистры процессора с текущего АЛУ на любое свободное и передаст выполнение этой сверхфункции на свободное АЛУ.

Так эта функция ещё и память читать/писать будет. Значит придётся ещё и всю память, которая может потенциально быть прочитана этой функцией, копировать.

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

OoO выполнение на процессоре делает то же самое без компилятора. А сишный компилятор для Эльбруса (lcc) делает это для C/C++.

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

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

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

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

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

Нет. Простые функции параллелить дорого, так как больше потеряешь на передачу данных на это отдельное АЛУ и результатов обратно.

Но именно этим и занимаются современные процессоры. Правда тут Lisp не особо как то выделяется на фоне других моделей.

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

В Lisp все же легко строить цепочки из всяких map filter reduce, а с этим уже можно работать. GC и рантайм по моему наоборот дело упрощают, хороший GC не обязан блокировать все потоки, и с ним можно реализовать паралельный скиплист, дополнительный рантайм может обеспечить транзационную память, зеленые потоки, независимые процессы как в Erlang.

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

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

Не думаю что кто то планировал запускать ровно одну ракету по США, так что нужно было обсчитывать много целей. И сразу с компьютерами появились военные меш-сети, где компьютер должен был обрабатывать свою информацию, передать ее в сеть, принимать чужую и встраивать в свою карту, и выбирать из множества целей приоритетную. Тут уже сотня точек и несколько заданий для каждой. Уже в 1951 году компьютер Whirlwind имел базовый параллелизм, и производительности жутко не хватало многие годы. Даже компьютер управления Аполлоном в 1966 уже оперировал задачами.

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

Так эта функция ещё и память читать/писать будет. Значит придётся ещё и всю память, которая может потенциально быть прочитана этой функцией, копировать.

Оперативная память может быть общей для всех АЛУ процессора, потому что одна «чистая» функция не может переписать значение переменной для другой «чистой» функции. Тогда и копировать ОЗУ смысла нет - пусть все «чистые» фунции читают себе данные из общей кучи ОЗУ. Запороть чужое никто не сможет и поэтому блокировки на доступ к ОЗУ не требуются, что значительно ускорит вычисления. В этом и есть выигрыш работы функциональных программ, что каждая из «чистых» функций сможет выполняться без остановки других таких же функций на разных АЛУ процессора одновременно.

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

В Lisp все же легко строить цепочки из всяких map filter reduce, а с этим уже можно работать.

Их везде легко строить. И джаваскриптовское

l.map(f1).filter(f2).take(n).reduce(k, f3)

читается легче, чем

(reduce k f3 (take (filter f2 (map f1 l)) n))

хороший GC не обязан блокировать все потоки, и с ним можно реализовать паралельный скиплист

Напишите. В современном лиспе, который SBCL, блокирует.

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

Тогда и язык нужен Erlang, а не Common Lisp.

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

Не думаю что кто то планировал запускать ровно одну ракету по США, так что нужно было обсчитывать много целей.

А для чего, по-твоему мнению, в процессоре «Эльбрус» сделали несколько АЛУ? - Чтобы одновременно умножать и складывать несколько чисел за такт работы процессора. Сверхзвуковых ракет ещё не было и однопоточной производительности «Эльбруса» вполне хватало, чтобы посчитать быстрое преобразование Фурье и обнаружить все цели одновременно.

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

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

читается легче, чем ...

Есть же эмуляторы стека (оператор стрелки), и compose.

Напишите. В современном лиспе, который SBCL, блокирует.

Речь же про возможности, а не про нынешнее состояние, и год упоминался 1970, еще до появления CL.

Тогда и язык нужен Erlang, а не Common Lisp.

Текущий лисп на помойку конечно, но многие идеи хороши.

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

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

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

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

Тогда и язык нужен Erlang, а не Common Lisp.

Текущий лисп на помойку конечно, но многие идеи хороши.

есть какой-то диалект лиспа поверх эрланга, например LFE = Lisp Flavoured Erlang. сам эрланг это почти как пролог только вот это «let it fail» – более функциональный и акторный, чем логически декларативный.

кстати, вот тебе в тему того треда про «почему консы в лиспе, а не массивы или вектора».

если честно, тоже не до конца понятно.

например, clojure – лисп под JVM в котором [вектор] в качестве лисп-форм применяется чуть ли не чаще чем в (список). опять же, есть своё понимание многозадачности, и все батарейки из стандартной JVM.

есть ещё вот такой лисп: LispE от того же автора, который написал ещё и tamgu

если tamgu можно примерно уподобить PopLog, только более осовремененный (общее ядро, на базе которого написаны реализации ML (SML, Ocaml), Lisp, Prolog).

то LispE полезно сравнивать с Shen/Qi , Tamgu/PopLog и Clojure с векторами. кстати, в Book Of Shen расписано примерно что такое Shen/Qi, какая связь с первоначальной версией на CL, sequent calculus, правилами вывода и типизацией и оптимизацией пролога. если коротко, то Shen – это развитие идей Qi. Shen более минималистичное лисп/прологовое ядро, где поверх лиспа есть реализация пролога, Yacc транслятора и т.п.

вводная часть и исторический контекст в Book Of Shen понравились: если в одном направлении – прокачивали машину Тьюринга в архитектуру фон-неймана CPU и брейнфак-машину (отличающуюся только количеством регистров в этой машине Тьюринга), то в другом направлении – прокачивали архитектуры виртуальных машин для частично-рекурсивных функций – SECD машина как форт с несколькими (4, емнип) стеками; пролог WAM-машина; SML/Ocaml ZAM, CAM категориальная абстрактная машина.

такая вот «механизация лямбда-исчисления» (развитие SECD и LispKit).

из lispE/wiki/1.-Introduction:

многопоточный (multithreaded) интерпретатор лиспа на С++11 с наследованием С++ классов и STL шаблонами:

The basic principle of our interpreter is the following:

All the elements of the language are implemented as classes derived from the class: Element In this way, a list in Lisp can be implemented as a vector of Element .

Basic types We also used the basic C++11 types to implement our different elements:

есть интеграция с питоном, ленивые вычисления в духе хаскеля, сокеты, sqlite, xml, json и прочие батарейки.

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

в общем, если обсудить эту реализацию «лиспа на массивах или векторах» (или С++ объектах с STL шаблонами).

вот можно ли это, например, развить не в «метациклический интерпретатор лиспа через eval/apply/evlis вычисляет alist и assoc для окружения в evlis, поэтому ему нужны консы и списки, поэтому бесконечные рекурсивые структуры данных с car/cdr и gc»

а в clojure-подобное «метациклический интерпретатора лиспа вычисляет окружения и формы, спецформы не через списки и консы, а через вектора и массивы» – в том числе, через CUDA на GPU, на шейдерах, например?

у clojure есть STM как транзакционная память, persistence и функциональные структуры данных и своё понимание многозадачности, понимаю. да и сама JVM в качестве батареек. и многопоточность оттудова.

кстати, вот увидел на википедии что есть реализации Clojure VM в том числе и на Rust и под Erlang.

здесь есть реализация на С++ с тредами, объектами и STL шаблонами. и батарейками через С++ и хаскелеподобные ленивые списки.

и через вектора с массивами.

кстати, в контексте этого вспоминается InteLib небезызвестного А. В. Столярова. где одна простая идея: «алгебра программ» на лиспе в виде S-выражений транспилируется (частично в runtime, частично в compile-time) в С++ объекты и выражения с перегруженными операторами (прочитай у него же про «операцию пробел», например).

здесь на мой взгляд, слишком много реализовано в runtime. хотя, если бы брать например D, D2 с PEG-парсером PEGGED. то там в CTFE происходит транспиляция довольно нетривиальных грамматик (паскаль, оберон, модула, питон, js и т.п.)

то есть, в языке с нормальной модульностью и рефлексией (D2 а не С++) бОльшая часть работы может проходить в compile-time через CTFE.

вот же примеры: Clojure, LispE или перекачанный в направлении развития LispE InteLib А. В. Столярова.

и что-то типа реализации Shen/Qi поверх этого.

собственно, дискасс. что такого принципиально мешает вычислять лисп на шейдерах, GPU и CUDA с векторами? и/или, даже тензорами?

почему тут некий энтузиаст пишет всю дорогу, что «под лисп нужен принципиально другой лисп-процессор»?

почему нельзя обойтись тензорами или векторами на GPU, шейдерами для реализации SECD-машины, например?

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

lol лиспа что изначально это ассемблер(и даже машкод) в котором код и данные есть коданные и даныкода

лисп – это программируемый язык программирования.

если ассемблер это язык опкодов записанных мнемоникой, буквально, язык-сборщик в опкоды, довольно невысокоуровневый; макроассемблер это МЕТАязык (которым думаем) над объектным языком (о котором думаем) сборочного языка опкодов.

то что на МЕТА этого уровня можно описать конструкции алгоритмов (циклы, ветвления, вызовы подпрограмм) – не делает этот макроассемблер языком высокого уровня.

всё равно: возьми например HLA от Randy Hyde из «Art Of Assembly»

у его макроязыка довольно навороченный CTFE даже по сравнению с MASM / PDP-11 masm и потомками. например, пример printf через CTFE из stdlib HLA. там есть рефлексия, typeinfo (пусть и костылями в духе ручной С++ сериализации).

но само наличие invoke, cdecl call, while..wend, for..endfor, repeat..until, if/then/else – НЕ ДЕЛАЕТ его высокоуровневым.

язык всё ещё низкоуровневый – всё ещё ассемблер. стоит посмотреть на HLAbasic – там вся информация о типах размером явно прописанных ассемблерных машинных слов.

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

тоже самое и в форте, со встроенным ассемблером, например. да, в духе Taygeta, J.V. Noble – можно реализовать фортран на форте. с закатом солнца вручную написанного через immediate слова транспилятора формульных выражений.

что происходит в этом как бы ЯВУ по форме алгоритмов с ЯВУ структур данных? они всё ещё низкоуровневы.

если есть first class object := first class types, first class environment (например, bindings, namespaces, «словарей форта/постскрипта и т.п.» - например, Display PostScript это SECD машина функционального форта со словарями и 4-6 стеками: данных, управления, словарей (bindings/namespaces), gsave/grestore graphic context, events, messages, – ЕМНИП, как-то так; SECD это не двухстековый а 4-стековый форт; DPS это 6-стековый или больше функциональный форт; объекты в DPS сделаны через словари, а могли бы быть лямбдами и замыканиями, продолжениями и т.п.)

поэтому «по-настоящему (без дураков) высокоуровневый ЯВУ» – это тот, в которым типы данных как first class objects, где objects:=types, environments, bindings, namespaces, object classes, object instances, exceptions, actors, clojures/continuations/coroutines, and so on, so on, so on…

ну типа документ-литерал (а не строка-литерал) в том же CURL, ‘the gentle slope web language’ – который тоже лисп, просто с tcl-ным синтаксисом.

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

лисп это метаязык как носитель алгебры программ; это очевидно в случае ассемблера на схеме (эта алгебра списками); ассемблера на форте (тогда эта алгебра стеками); ассемблера на APL/J/K/Kxdb/а может, даже и мумпсов – тогда это алгебра тензорами: массивами и векторами.

собственно. линейный лисп из recursive.pdf изначального определения на 37 страниц Джона МакКарти. это про такой вот матрично-тензорный лисп без GC, в какой-то мере.

за счёт однородности представления различных форм этой алгебры кода программ и коалгебры кода данных – эта гомоиконность позволяет раскрутившись метацикличностью делать eval/apply/evlis, окружение на бесконечных частично рекурсивных частично а не тотально определённых ЧРФ; само определение лиспа из recursive.pdf на 37 страниц – это такое рекурсивное-метациклическое ЧРФ определение.

то что функции определены не тотально а частично (например, car атома а не car списка что должен выдавать? правильно, NULL/NaN/буддисткое «му» – это определение в этой точке не существует)

– не мешает всё это взаимно рекурсивно метациклически вычислять.

если те выколотые точки где не определено – метациклическим eval/apply/evlis не используются.

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

в этом смысле, вот например в алголе – есть идентификаторы с пробелами.

открываем советский ГОСТ по алголу-68 и видим «именные группы многословных идентификаторов приводятся к канонической форме где предпочтительно средний род в именительном падеже в основной лемме словоформы».

есть например, stropped представление; если бы правила WG-грамматик алгола-68 были например, написаны на лиспе (и далее же в лисп, то есть, GCC MELT транспилировались – вместо Algol68toC / Marst /Algol68C транспилироваться в GCC MELT который лисп макросами лиспа, который правила трансляции WG-грамматик и кодогенерироваться через CFG/SSA GCC-овым бекендом)

– и их можно было бы невозбранно расширять, то этот «идентификатор с пробелами» – можно например представить в виде DSL.

например, многоязычные глубинной семантики формы notion, metanotin, hyperrules, metarules WG-грамматики – разные англо/немецкие/советские/китайские иероглифы транслируются в одни и те же глубинные структуры типа апресяна, мельника, компрено универсальной семантики.

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

или, например, придумать миниDSL для «классово-объектных идентификаторов» типа «многословного идентификатора с пробелами» «класс FOO объект BAR метод WTF аргументы (список, кейворды, баззворды и всё такое)»

транспилировать в VMT, VTable, VPTR, CTFE конструкции для мемоизации диспетчеризации Virtual/NonVirtual манглед методов.

то есть, транспилятор в С++ объектную модель преимущественно в компайл тайме.

или, транспилятор в CLOS метаобъектного протокола из пустоты модель преимущественно в рантайме.

лисп и форт оба инструменты будущего в прошедшем

в грядущее прошлое смотрит маг…
… и слышно пение в двух мирах

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

т.е. для лиспа в массы нужен какой то синтаксис - например уже давно почивший dylan

в целом Lisp это хороший lvm (language of virtual machine)

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

а вот в SML/Ocaml например, категориальная абстрактная, а не виртуальная лямбда-машина.

которая более эффективная. так lvm хороший, но мог бы быть и cam лучше.

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

интересует знать, а не просто уметь : «смысл<->текст» и объяснительная модель какое правило денотата глубинной семантики сработало из поверхностного синтаксиса и почему, объяснительная модель этих правил.

и её метаобъектный протокол, например.

а не просто некоторый сэволюционировавший ч0рный ящик с 1000B параметров и структурой слоёв коннектома соединённых ХЗ как и почему и главное – зачем.

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

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

они переделали эль-барроуз из берроуза с мейнфреймом на алголе в тегированной архитектуры типизированную контент-аддрессабле-CAM-машину как супернавороченную брейнфак-машину Тьюринга с суперскаляром и OoOO Бабаяна и эль-76 автокодом алгололиспом Пентковского. была ли там SECD-машина и возможно даже тензорно-векторная, вот в чём вопрос.

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

апресяна, мельника, компрено

апресяна, мельчука, компрено

джона уилкиса блиссимволики

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

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

кстати, вот тебе в тему того треда про «почему консы в лиспе, а не массивы или вектора».

Потому что рекурсивную обработку на консах делать проще, чем на векторах. Поэтому и в Haskell основная структура список на консах, причём там даже строки являются списками. И в Erlang тоже.

есть ещё вот такой лисп: LispE от того же автора, который написал ещё и tamgu

Скорость. Common Lisp имеет реализацию достаточно быстрого компилятора (SBCL). Scheme тоже (Chez и Racket). Остальные лиспы заметно медленнее.

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

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

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

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

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

Твоя одновесельная резиновая лодка это программный преобразователь-переводчик Лисп-исходников в ассемблер видеокарты. Двухвесельная железная лодка это отточенный «плюсистами» такой же программный преобразователь. А спортивная многовесельная ладья это сочетание компилятора Лиспа с многоалушным Лисп-процессором.

Поддержка сочетания фунциональщины в компиляторе и электронной аппаратуре это еще и возможность сберечь электроэнергию при массовых вычислениях в вычислительных центрах, ибо мощности ГЭСов и АЭСов не безграничны. Да и вывести функциональное программирование из тени можно будет таким способом. Если исходные коды функциональных программ будет создавать кодогенератор, то программисты среднего уровня смогут выполнять свою работу без труда.

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

В Lisp все же легко строить цепочки из всяких map filter reduce, а с этим уже можно работать. GC и рантайм по моему наоборот дело упрощают, хороший GC не обязан блокировать все потоки, и с ним можно реализовать паралельный скиплист, дополнительный рантайм может обеспечить транзационную память, зеленые потоки, независимые процессы как в Erlang.

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

Эрланг - это по сути воплощение ООП из книги SICP. Вот сидят себе миллион процессов, каждый со своим сборщиком мусора. И у каждого процесса есть свои очереди (аж по 2 штуки на процесс). Сообщения от других процессов попадают в эти очереди. И тогда состояние эрланговского процесса становится воплощением состояния бесконечного марковского процесса, который перескакивает из одних состояний в другие во время реагирования на такие сообщения.

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

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

Это позволяет запускать сборку мусора для каждого процесса независимо. Отсюда и способность к «мягкому режиму реального времени».

Все это красиво сделано в эрланге, но это очень узкоспециализированная вещь!


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

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

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


К сожалению, идеала по-прежнему нет

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

машина Тьюринга фуфло (угрёбищная брейнфак-машина согласно теореме Лавсана)

вот, тачка на прокачку: машина Кирдина – фсему голова!!!

finit_det_kird

и ещё kirdalun

отсюдова с книжками

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

я что-то не понял, это ещё трансформеры или уже автоботы с десептиконами?

это генератырь/дискриминатырь описаны современных LLM-ок примерно в 1998 году ещё?

или ещё не совсем?

кстати. КМК – она тоже метациклична и потому менее угрёбищна?

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

джаваскриптовское

l.map(f1).filter(f2).take(n).reduce(k, f3)

читается легче, чем

(reduce k f3 (take (filter f2 (map f1 l)) n))

Которое лёгким движением руки превращается в

(-> l
    (map f1) 
    (filter f2)
    (reduce f3 k))

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

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

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

А вот это уже не лёгким движением руки, а отказом от map/filter/reduce из стандартной библиотеки (продолжений в Common Lisp нет, поэтому даже порядок обработки невозможно поменять).

А если отказываться, то трансдьюсеры и в JS есть.

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

отказом от map/filter/reduce из стандартной библиотеки

Шо? Это те же самые функции из стандартной библиотеки. Всю магию делает макрос ->.

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

Это было больше на схему похоже, чем на cl. К тому monk-у очень нравится схема.

Кстати, а в России на кложуре за деньги пишут? Для себя я могу на чем угодно писать, хоть на смолтоке, форте или прологе. Интересует коммерческое применение. По hh.ru данных открытых нет

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

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

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

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

Вообще, в кложуре очень даже чувствуется бережное отношение к духу common lisp

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

Кстати, а в России на кложуре за деньги пишут? Для себя я могу на чем угодно писать, хоть на смолтоке, форте или прологе. Интересует коммерческое применение. По hh.ru данных открытых нет

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

В самой РФ по-моему никогда не было вакансий.

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

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

В clojure в другую сторону страдать: бинарный файл не сделать

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

Не понимаю, почему жабисты до сих пор не запилили сборку бинарей а-ля дотнет — всё в одном файле вместе с рантаймом. Может, настолько прямо необходимая фича, что всем наплевать. А может, и запилили, просто я об этом не знаю %)

сишную разделяемую библиотеку не открыть

Да ладно, если так уж хочется настоящих сегфолтов — в жабе есть для этого средства, насколько я знаю. Есть в жабе — есть и в кложе.

рестартов нет

Чего нет — того нет. Правда, неясно, насколько оно вообще нужно в реальной жызни.

CLOS нет

Обобщённые функции (мультиметоды) есть (посложнее и помедленнее ), протоколы есть (попроще и побыстрее), ad-hoc иерархии есть. А если прям хочется подолбиться в хардкорный и бескомпромиссный ООП — в нижележащей жабе его охапками, доступного через интероп.

Миф про динамические переменные уже развеяли, а setf в кложе скорее баг, чем фича.

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

Да ладно, если так уж хочется настоящих сегфолтов — в жабе есть для этого средства, насколько я знаю. Есть в жабе — есть и в кложе.

Там прослойка требуется со стороны Си. Примерно как с библиотеками Си++ в остальных языках работать.

Обобщённые функции (мультиметоды) есть (посложнее и помедленнее ), протоколы есть (попроще и побыстрее), ad-hoc иерархии есть.

То немножко не то. Возможности добавить к существующей обобщённой функции новые методы нет, before/after/around и комбинаторов методов нет, метаобъектов нет.

Жабский ООП ещё более куцый.

Миф про динамические переменные уже развеяли, а setf в кложе скорее баг, чем фича.

Так смысл динамических переменных именно в том, что их можно менять через setf и не запороть значение для всех остальных. Как CLOS — правильный ООП, так динамические переменные — правильные глобальные переменные (а в современных ЯП решили запретить и то и другое).

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

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

Хм, а я всегда их через let менял и думал, что все остальные также делают…

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

Хм, а я всегда их через let менял и думал, что все остальные также делают…

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

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

А defparameter не подходит? Ты про top-level?

Подходит. setf в него для специальных переменных вроде и раскрывается.

Не top-level, а изменение изнутри функций. Собственно, зачем глобальные переменные и нужны. Когда из одного потока, например, можно изменить поведение всех потоков.

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

Возможности добавить к существующей обобщённой функции новые методы нет

Да лааааадно? А defmethod тогда что делает?

before/after/around и комбинаторов методов нет

И ненужно. Нет, серьёзно, эту говнину потом гарантированно сам автор не поймёт через две недели — не зря в емаксе не рекомендуют адвайсами пользоваться, ой не зря.

Проще надо быть %)

Так смысл динамических переменных именно в том, что их можно менять через setf

Для этого binding есть же.

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

надо из того куска кода поменять значение по умолчанию (а также для других потоков)

А можна нинада? А то гадай потом в отладчике — какой обормот опять шатает общую переменную, безнаказанно и бесследно.

В кложе есть множество интересных штук для организации взаимодействия потоков, с человеческим лицом — атомы/рефы/агенты, промисы, core.async.

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

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

Это больше смахивает на какой-то канал, через который ты распространяешь новые значения среди потоков исполнения

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

Это больше смахивает на какой-то канал, через который ты распространяешь новые значения среди потоков исполнения

Среди всей программы (возможно многопоточной).

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

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

Да лааааадно? А defmethod тогда что делает?

Что-то с памятью моей стало. Помню (или так думаю), что когда-то читал, что defmulti все методы в своём теле определяет. Может было очень давно, может неправда.

И ненужно. Нет, серьёзно, эту говнину потом гарантированно сам автор не поймёт через две недели

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

Например, хочу я шрифт на кнопках покрупнее сделать. В CL просто добавлю after после on-paint в класс button. А в любом другом надо или менять библиотечную функцию или менять весь код, который выводит кнопки (включая библиотечный), чтобы вместо button выводил my-button.

Для этого binding есть же.

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

В Common Lisp у меня просто (setf *logger* new-logger). При этом не ломается нигде, где механизм указан явно или указано, что механизм должен не меняться для блока через (let ((*logger* *logger*)) ...). В современных языках «делайте синглтон и либо всю программу в него, либо тащите его как параметр повсюду». Как в Clojure?

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

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

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

хочу я шрифт на кнопках покрупнее сделать

в любом другом надо или менять библиотечную функцию или менять весь код

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

It is all data (tm).

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

В Clojure я бы определил логгер как отдельный компонент системы (собираемой с помощью DI-контейнера типа Component/Integrant/Mount/…, в которой логгер будет передан остальным компонентам как зависимость) и при изменении настроек логирования просто явно перезапускал его с новыми настройками, а не шаманил какие-то неотслеживаемые подковёрные манипуляции с глобальными переменными.

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

Это больше смахивает на какой-то канал, через который ты распространяешь новые значения среди потоков исполнения

Среди всей программы (возможно многопоточной).

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

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

Я вообще, к многопоточке осторожно отношусь. Видел не раз, как под нагрузкой вылезает все то, чего никогда не встретишь при обычной нагрузке (потому и C вместе с C++ недолюбливаю - там это проявляется особо ярко, там напороться на ошибку особенно высокие шансы)

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

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

Можно выводить куда-то файл/строку кода/новое значение.

при изменении настроек логирования просто явно перезапускал его с новыми настройками

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

Но надо городить отдельный компонент (причём, в идеале, по компоненту на каждый аналог глобальной переменной).

monk ★★★★★
()