Pull to refresh

Comments 136

UFO just landed and posted this here
>Появление Java создало этому препятствие, поскольку JVM плохо взаимодействует со Scheme/Lisp/Racket.
Хм. Ему же все равно, какой из диалектов лиспа применять, он сам это написал. Ну взял бы кложу, и не мутил воду?

И вообще, автор (я вижу что это перевод, претензия не к переводу) фанат лиспа в плохом смысле этого слова. Мне было достаточно вот этого, например:

>Другими словами, статическая типизация бессмысленна.

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

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

Я сделал этот перевод специально для того, чтобы разнообразие мнений спровоцировало обсуждение
>Там далее он упоминает, что собирается попробовать Clojure, и вероятно добавит её к своему стеку.
Ну да, а еще пишет, что там нет ТСО. Я могу это понять, это аргумент разумный.
Меня беспокоит, что человек, который защитил докторскую на монадах, считает статическую типизацию бессмысленной и довольно категорично это утверждает. Я не питаю иллюзий относительно своих знаний — они, разумеется, куда меньше, поэтому хочется быть уверенным, что все его резоны достаточно внимательно рассмотрены
Ну, у меня есть простое предположение. И исхожу из того, что есть разные проекты и задачи. Возможно у него такие задачи, где статическая типизация слабо помогает.

Ну так, условно немного — если у вас в задаче скажем 90% это взаимодействие с внешней системой, типа СУБД, где в общем-то нет статической типизации, потому что никто не мешает сделать ALTER TABLE… и поменять набор, названия и типы данных колонок, и при следующем запросе вы будете иметь дело с данными другого типа (обычно так никто не делает, но случаи тем не менее бывают разные), то конкретно в этом месте вам типизация помогает мало. По сути, вы работаете с чем-то типа строки, иногда преобразуя ее во что-то еще.

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

Ну или другими словами — есть же задачи, которые более-менее решаются языками типа unix shell, где типов данных тоже нет никаких. Замените шелл на lisp — и весьма вероятно, станет лучше. Главное — не обобщать это на другие типы задач :)
Ну или другими словами — есть же задачи, которые более-менее решаются языками типа unix shell, где типов данных тоже нет никаких.

И это ужасно.

Ну в общем да. Если меня спросить — то лучше на шелле не писать вообще ничего, кроме самого минимума. Но я в том смысле, что есть задачи, для которых и таких языков как-то хватает.
UFO just landed and posted this here
И еще данных сложнее тривиального одномерного массива. Ну т.е. я бы не сказал что это прям ужасно, но надо четко понимать ограничения такого языка.
Я подумываю написать автору и спросить про решаемые им задачи. Может станет более понятно (ну или мы будем удивлены)
Ну, судя по его послужному списку, это не похоже на какой-то глупый вброс.

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

Ну, как пример — Пол Грэм, насколько я понимаю, сегодня занимается тем, что выращивает стартапы. Вполне возможно уже лет 10 ничего не пишет ни на каком языке (это исключительно предположение, не более). Не практик, иными словами. Стоит ли верить его текстам про лисп, да еще и написанным 15 лет назад? Ну или точнее так — стоит ли им безоговорочно верить? Ведь опыт любого человека ограничен, даже самого крутого специалиста.
Среди комментариев к оригинальной статье я нашел его довольно итересный ответ, который думаю стоит тут перевести:

"… я понимаю, что моя позиция несколько противоречит людям, которые любят статичные типы, и всегда вызывает оживленные дискуссии. В конце концов, я пришел к этому после многих лет работы с Мирандой, и даже сегодня я много работаю со Scala. Итак, я хорошо знаком с последними и лучшими достижениями в области статической типизации, но в конце концов, сравнивая Scheme с Miranda и Racket с Scala, я не могу не прийти к выводу, что статическая типизация ничего не дает для меня или для инженеров проектов, которые мы берем на себя. Во всяком случае, разработка на Scala идет намного медленнее без каких-либо гарантий качества.
Хотя я говорю только из личного опыта, другие пытались применить к этому более научный подход. Я столкнулся с этим докладом, где докладчик на самом деле потратил некоторое время на измерение эффектов статических и динамических типов. Доклад долгий (почти час), но он хорошо говорит и поднимает некоторые интересные моменты. Ничего сверхубедительного, конечно, но заставит задуматься. vimeo.com/74354480 "

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

Из этих комментариев я также узнал что Дэниел Фридман написал две «общедоступные» книги о типах: «The Little Prover» and «The Little Typer.». (Это, конечно отсылка к «The Little Schemer» и The «Seasoned Schemer». Пожалуй я добавлю их к своему списку чтения, поближе к его началу). Автор статьи говорит что хорошо знаком с этими книгами, поэтому, я думаю что он куда квалифицированнее чем я может судить о типах. Он считает, что типы «требуют слишком много работы прямо сейчас, чтобы быть полезными в повседневной жизни». Он также ссылается на соответствующее исследование: web.cs.ucdavis.edu/~filkov/papers/lang_github.pdf которое я еще не читал.

Вот его взгляд, который он раскрывает в одном из комментариев:

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

Он также провел собственное исследование, которое я тоже приведу здесь:

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

По сути, это векторная библиотека с множеством функций высшего порядка для машинного обучения. Таким образом, этот модуль не очень большой, всего около 256 строк с примерно 150 строками реального кода, с дополнительными 100 строками кода с примерно 50 тестами. Возможно, мне потребовалось бы 3-4 часа, чтобы написать код и в целом написать для него тесты. В racket очень легко преобразовать из нетипизированного в типизированный, поэтому я сделал преобразование. Средство проверки типов Racket в значительной степени современное, так что это ни в коем случае не примитивный инструмент.

Средство проверки типов выдало мне 74 ошибки без какой-либо видимой мне причины (большую часть времени он не мог определить типы, поэтому он хотел, чтобы я объявил некоторые вещи, в некоторых других случаях требовалась дополнительная реструктуризация и объявления). Предполагая, что обработка каждой из этих ошибок занимает примерно 5 минут, я оцениваю общие накладные расходы в 370 минут, что составляет примерно 6 часов. Давайте будем щедры на мою способность отлаживать ошибки типа и возьмем половину этого времени. 3 часа. то есть время, затрачиваемое на доставку модуля, удвоилось, а выгода, которая у меня есть, равна нулю. Итак, прямо сейчас у моего средства проверки типов чистые отрицательные накладные расходы составляют 50%. Даже через 1 час (что, по моему опыту, является исключением, поскольку для поиска ошибок типа требуется достаточно глубокое отслеживание кода), это будет значительными накладными расходами в размере 20%.

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

Я очень рекомендую вам самому попробовать подобные эксперименты. Мы сможем сравнить записи в будущем. Но вам придется использовать такой язык, как Racket, чтобы легко переключаться с типизированного на нетипизированный и обратно… Кто знает, возможно, вы никогда не вернетесь обратно к типизированному :)
>Во всяком случае, разработка на Scala идет намного медленнее
Чем на лиспе? Хм. Ну я не знаю, у меня примерно одинаковый опыт в лиспе и скале, и я бы не подтвердил такое утверждение.
Я думаю, вы недостаточно квалифицированы… в лиспе. Это провокация, не принимайте всерьез :) Или наоборот, принимайте всерьез, тогда нас ждет интересное обсуждение дальше )
Я же пытаюсь сравнивать, и Scala у меня тоже не основной язык. Ну т.е., опыт в них у меня примерно одинаковый (правда, не считая того, что на скале я начал писать лет на 10 позже, то есть я сам был опытнее на тот момент).

То есть, это по любому чисто субъективное утверждение, в которое можно сразу вносить поправку на то, что я предпочитаю статически типизированные языки, и свежий опыт у меня как раз в работе на них. Ну т.е., то что мне скала подсказывает в отношении типов в программе, мне как раз очень даже помогает.
Никудышный из меня тролль (
А если не секрет, то что делали на лиспе, где его применяли?
Ну это был достаточно специфичный лисп — в AotoCAD. И делал я на нем совсем один систему для расчета характеристик сети цифрового телевидения. Ну т.е. вы рисуете в AutoCAD структуру сети, устанавливая туда усилители, разветвители, и т.п., соединяя их разными кабелями, и получаете качество сигнала в произвольной точке. Ну т.е. база данных компонентов, плюс бизнес логика расчетов, плюс само рисование. В общем, это был достаточно забавный опыт.

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

Могу сказать, что в какой-то момент в AutoDesk решили, что лисп им не очень нравится, и нужно разрешить писать на других языках — и в итоге там появилась поддержка .Net — и кое-что на C# было делать несколько проще, или так скажем — привычнее. Но в целом я все равно предпочел бы лисп. Для такого рода задач.
Я думаю это очень неревантный опыт. Я бы даже сказал что для «программирования» он практически не должен учитываться, т.к. важные лиспо-концепции отстутствуют в АвтоКаде. У них там просто скриптинг в оболочке скобочек (могу ошибаться, т.к. не сам изучал)
Важные концепции? Это какие же?
макросы, condition/restarts, мультиметоды из CLOS, много их…
Ну, это конечно хорошие вещи все, но я бы не сказал, что они являются прям жизненно важными для лиспа. Во всяком случае, я бы считал фундаментальными те вещи, который были изначально у Маккарти. А остальными — то, что можно реализовать самому, имея в наличии только базовые. Или то, без чего вообще можно программировать — а это в общем-то очень маленькое подмножество для лиспа.

А то может получиться, что и скажем кложа — тоже неревантна, потому что она не CLOS?
Это совсем неверно. Это как считать что есть жизненно-важная концепция указателей в изначальном си, а остальное можно реализовать самому и получить С++ 20. Или даже джаву. Ну… формально можно, на практике никто этим заниматься не будет.

Лисп прошел 60 лет развития, лисп МаКарти и современный лисп — это как палка-копалка в сравнении с современным комбайном.

Кложа определенно проигрывает Common Lisp. Но это не ее вина, она вынуждена приспосабливаться к JVM
Ну, как неверно. Если у языка есть ядро, на котором можно написать все остальное, и это будет несложно и эффективно — то это ядро и есть самое важное в языке. Во всяком случае, такой взгляд на вещи существует, и имеет какое-то право на жизнь.

Понятно, что если я при этом никогда не пользовался лисповскими макросами — то да, я конечно же их не знаю. Но по сравнению с человеком, который вообще на лиспе не писал — я их освою сильно быстрее. Или даже — смогу реализовать сам, если уж приспичит.
Вы ошибаетесь. Для реализации всех этих возможностей вам придется приобрести квалификацию разработчика языка, думаю на это уйдет от пяти лет. Это лишь кажется простым
Ну, как бы это сказать… это же в лиспе у меня опыта года четыре, а вообще-то его лет эдак за 40. И интерпретатор лиспа, во всяком случае простой, я бы написал бы спокойно, была бы такая задача поставлена.
Я тоже так думал. Действительно, простой интерпретатор я написал почти сразу, это легко, особенно если делать его на языке со сборщиком мусора. А вот чтобы сделать современный лисп, да еще и компилируемый я учусь уже несколько лет и понимаю, что это совсем непросто. Фичи этого языка очень сбалансированы и тщательно увязаны друг с другом, я рекомендую прочесть (даже по диагонали) lisper.ru/pcl для погружения в тему.
Так а кто говорил, что я в процессе не буду подсматривать в умные книжки :)? Буду, обязательно.
Тогда предлагаю объединить усилия ) Вместе заниматься разработкой языков куда интереснее!
UFO just landed and posted this here
Есть же интересный пример на тему статической vs динамической типизации. Возьмите ваш статический язык и накишите-ка класс Number. Не Integer, не Float, не Rational, и никакой другой частный случай — но чтобы Number мог быть и десятичной дробью, и натуральной итд.
То что в Лиспе есть из коробки, в какой-нибудь Java — не есть возможным в принципе.
UFO just landed and posted this here
UFO just landed and posted this here
1. Не уверен, что это правильно. Integer, Float и Rational должен использоваться по-разному в разных ситуациях. Объединить их в один тип мне кажется очень большой архитектурной ошибкой. Всё равно в процессе работы с ним придётся уточнять, что это на самом деле и в зависимости от этого делать разные действия.
2. В принципе, это возможно. Тип Object.
3. Можно сделать базовый класс Number.
UFO just landed and posted this here
У автора есть мнение, оно не обязательно правильное. Особенно если учесть, что правильность может сильно зависеть от среды и решаемых задач. 15 лет назад заявив о том что ООП — чушь можно было неплохо согреться. Это еще не повод не читать дальше, на самом деле
UFO just landed and posted this here
Довольно интересно, не знал про RLisp. Это интерпретируемый диалект?
UFO just landed and posted this here
«Как и большинство старых Lisp, на первом этапе PSL компилирует код Lisp в код LAP, который является еще одним кроссплатформенным языком. Однако там, где более старые Lisp в основном компилировали LAP непосредственно на язык ассемблера или в какой-либо промежуточный продукт, зависящий от архитектуры, PSL компилирует LAP в код C, который будет выполняться на языке виртуальной машины; поэтому программы, написанные на нем, в принципе так же переносимы, как и C, что довольно переносимо. „

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

Если я правильно понимаю, сегодня оно живет тут.
Спасибо, сейчас изучим
Как давнего пользователя (и активного сторонника) Scheme/Common Lisp/Racket, меня иногда спрашивают, почему я предпочитаю их. К счастью, я всегда возглавлял собственные инженерные организации, поэтому мне никогда не приходилось оправдывать это перед руководством.


Главное почему еще использует этот язык.
Еще никого не уволили за Java? ©

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

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

P.S. Как пример https://racket-lang.org/

http://rosettacode.org/wiki/Category:Racket
http://rosettacode.org/wiki/Category:Common_Lisp
http://rosettacode.org/wiki/Category:Clojure
http://rosettacode.org/wiki/Category:Scheme

Guile is a programming language

Или, как в старой шутке, что «умерло» — не может умереть? :)

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


Ну а умереть может легко: надо привлекать свежую кровь или когда уйдут на покой мастодонты вроде автора некому будет продолжать их дело.


P.S. Racket меня больше всего привлекал как раз из-за наличия типизированного диалекта.

>>> import this

Beautiful is better than ugly.

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

Да ладно? Скажите это разработчикам Grammarly :)

Возможно, они предпочитают ugly over beautiful. Разные люди имеют разные предпочтения.

А можно как-то аргументировать, что Lisp is ugly? Исключительно из-за скобочек что ли?


Потому что семантика Lisp и Scheme, пожалуй, одна из самых простых, логичных, ортогональных, гармоничных и красивых на сегодняшний день. CL и Scheme — это представители малочисленного семейства промышленных языков, для которых прописана формальная маьематическая семантика. Кроме них я знаю только Java, DOTTY и C-Light.


Собственно, поэтому и вопрос: чего такого ugly в Lisp-ах?

Основная причина, почему я (и не только я) считаю Лисп плохим — человеческая память ( точнее, особенность интерпретации скобок человеком (скобки подразумевают вложенный контекст, а у человека (обычного человека (пусть даже и знакомого с программированием (даже лиспом))) есть проблема с удержанием предыдущего контекста (если кто-то что-то пишет в скобках, то это интерпретируется как вложенный контекст (ну, вот этот текст, например (ну вы поняли зачем я это пишу))), так что если кто-то злоупотребляет скобками (извините, я не специально (на самом деле специально, to prove the point)), это страшно усложняет понимание) очень плохо переносит вложенные контексты ), и хоть лисп — не человеческий язык, переключение контекста (скобочки, скобочки) всё равно считываются как затруднение.

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

Это другие скобки. Скобки питона (они там есть, просто представляются как пробелы) описывают блоки. В Lisp'е же скобки фигурируют даже там, где большинство языков прекрасно обходится inline формой. Более того, унификация скобок выглядит элегантно в bnf'е, но фигово в голове.

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

например

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


в лиспе же наверно можно делать также и отступы вдобавок к скобкам, чтобы понятнее было?

ещё возможно было бы удобно посмотреть на код на лиспе и других языках программирования в виде «дерева». нету ли такого инструмента, чтобы так показывал, для какого-либо языка программирования?
Заметьте, вы легко написали этот текст, а я легко его прочитал. Проблему со вложенностью преувеличиввют те, кто никогда ничего на lisp-ах не писал. Вложенность контекстов есть в любом языке, просто её маскируют. Тем, на самом деле, lisp и хорош: он не скрывает сложность. Поэтому все стремятся её уменьшить за счёт экономных алгоритмов и представлений данных. Поэтому программы на lisp часто одни из самых простых для понимания и чтения. Ну, таков мой опыт. Вы, конечно, не поверите, потому что, конечно, даже не будете пробовать. Ну… Жаль, что из-за предрассудков люди игнорируют такой отличный инструмент. Впрочем, lisp-ы настолько продуктивны, что для поддержки и развития экосистемы много людей и не нужно.

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


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


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

Да ладно? Вы серьёзно? Rust, как пример сокрытия сложности? Rust по объёму boilerplate-кода сравним разве только с Си++. И вы путаете невозможность что-то абстрагировать (типа последовательности инструкций в другую конструкцию) со структурной сложностью алгоритма. С абстрагированием lisp-ы отлично справляются.

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

А можете объяснить, какая именно сложность здесь скрывается? По мне так, наоборот, здесь создаётся искусственная сложность, с которой потом идёт героическая борьба. При чём, сам способ борьбы выбран тоже очень сложным, не только для пользователя, но и для реализации. До сих пор, ведь, вылезают хитрые баги в borrow checker. Кстати, как там с доказательством того, что система типов в Rust sound? Помнится, грозились на Coq это доказать. Но полгода назад это дело не шибко продвигалось, статьи не брали в журналы (почему, интересно?). Что-то изменилось?

Легко можно было бы всё кардинально упростить: неизменяемые данные и явный обмен ими через атомарные ячейки. Так уже давным давно (лет уж 37 как: 1983 год, компилятор SISAL) делают многие языки программирования, не перекладывая на программиста работу компилятора по анализу потока данных и выбору оптимальных представлений.

Впрочем, кто я, чтобы кого-то переубеждать? Если Rust для вас работает, отлично. Но и Lisp для многих работает. Это не архивный инструмент. На нём и в наши дни пишут кучу классных приложений: от операционных систем до редакторов музыки для композиторов.

Смотрите. У нас есть данные в памяти. В какой-то момент мы хотим с ними что-то сделать. Допустим, у нас дерево и внутри дерева элемент, который мы только что нашли.


Если у нас тип данных имеет Copy, то мы (компилятор) может их побитово скопировать и мы можем это значение "вынуть" из enum'а или struct'а (который узел дерева) и работать с ним независимо. В момент, когда мы его "скопировали", оно "наше" и что хотим с ним то и делаем.


Теперь, предположим, что значение внутри структуры — не Copy. Причины почему оно может быть не Copy чуть ниже, а пока что последствия: если значение не copy, то мы не можем его "стырить", т.е. нам надо с ним работать in place. Мы не можем его "передать" чёрти куда (в соседний тред, например), потому что тред будет жить, а struct — уже нет. Мы можем его только borrow, при этом тщательно следить за тем, чтобы не сделать пересекающиеся ref mut, терпеть ругань компилятора и искать методы как с этим жить.


Почему мы можем не хотеть Copy? Ну, например, потому что данные большие. Если у нас внутри данных там array[f128;128], то будет медленно, причём, неожиданно медленно.


Или, это может быть специальная такая страшная память, которую нельзя двигать/копировать (DMA, io_uring, you name it). Или, она под Arc'ом и шарится между двумя тредами, и т.д.


Соответственно, если программисту все эти DMA до лампочки, то он говорит, что у него данные Copy, и работает с данными как на языке высокого уровня. А если, вдруг, оказывается, что нет — есть инструмент. Copy скрывает эту сложность (возню с non-copy memory), оставляя возможность туда нырнуть, если надо.


Это пример хорошо скрытой сложности.


Насчёт непротиворечивости и coq — это выше моего уровня, не могу ничего сказать.

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

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

Иногда создаётся ощущение, что из всей языковой братии только авторы Erlang, Lisp-ов и Go удосужились изучить CSP или pi-исчисление. Остальные же до сих пор пытаются параллелить имманентно последовательную семантику. Из-за чего вся эта боль и возникает.
Иногда создаётся ощущение, что из всей языковой братии только авторы Erlang, Lisp-ов и Go удосужились изучить CSP или pi-исчисление. Остальные же до сих пор пытаются параллелить имманентно последовательную семантику.
Спасибо вам за коммент Люблю почитать, но не имею профильного CS-образования. Я с трудом сформулировал проблему, а вы мне подкинули ключевые слова, по которым можно найти ответ :)
Преимущество префиксной и посфиксной нотации перед инфиксной в том, что если вам надо сложить миллион элементов то не надо писать 999999 знаков "+" что упрощает кодогенерацию. А без кодогенерации Лисп — не Лисп.

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

Beautiful is better than ugly.
Значит магические подчёркивания и self в каждом методе — это красиво?

self в каждом методе — это explicit is better than implicit. При чтении программы понятно откуда оно появилось. Плюс оно же позволяет при всяких извращениях в метапрограммировании и манкипатчинге менять методы класса на чёрти что (и иметь разумное поведение с первым аргументом=self).


Подчёркивания — грубовато, согласен, но лучше, чем если бы магические методы были без них (т.к. их было бы не различить).

При чтении программы понятно откуда оно появилось.
Я вызываю функцию с двумя аргументами, а объявляю почему-то с тремя. В данном случае, достаточно один раз посмотреть/прочитать вводный курс по языку, и больше какого-то объяснения уже не требуется. Зато писать/видеть придётся каждый раз.
Плюс оно же позволяет при всяких извращениях в метапрограммировании и манкипатчинге менять методы класса на чёрти что (и иметь разумное поведение с первым аргументом=self)
И чем это отличается от условного ruby?
Я вызываю функцию с двумя аргументами, а объявляю почему-то с тремя

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

Это внутренняя реализация языка, к которой я к тому же не имею прямого доступа, метод всё равно неявно связывается с исходным объектом.
Python:
class A:
    def __init__(self):
        self.v = 5

    def a(self, a):
        print('a =', a, 'v =', self.v)

class B:
    def __init__(self):
        self.v = 7
        
a = A()
b = B()
a.a(5)
b.a = a.a
print(b.v)
a = 5 v = 5
a = 5 v = 5
7

Lua:
a = { v = 5 }
function a.a(self, b)
    print('b = ', b, 'v =', self.v)
end
a:a(5)
b = { v = 7 }
b.a = a.a
b:a(5)
b = 5 v = 5
b = 5 v = 7
UFO just landed and posted this here

В питон завезли скорость и отсутствие GIL?

UFO just landed and posted this here

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

UFO just landed and posted this here
Насколько я знаю, никакого «быстрого» питона 3 без GIL нет, и перечисленные вами варианты не исключение — в pypy есть GIL, IronPython это python2, а Numba вообще свой диалект.
Я был бы рад, если бы такой вариант был, я изучаю питон недавно и был очень разочарован, когда узнал про GIL.

В текущем проекте использую Guile Scheme, как альтернативу Bash и Python. Удобнее Bash, потому что всё же, когда кроме строк есть другие типы данных, это существенно упрощает работу. Удобнее Python, потому что лучше сделана поддержка Posix и FFI, а current continuations позволяют проще и гибче программировать сложные потоки управления. Ну, и немаловажно, что код на Guile исполняется раз в 15 быстрее. Вероятно, у автора похожие причины.

Ну, есть еще просто вкусы в привычки. Я знаю и лисп и питон не идеально и примерно одинаково, и это не основные для меня языки. И если меня спросить, что я выберу — выбор будет в пользу лиспа.

У питона есть свои недостатки, и на мой взгляд, его сегодняшняя популярность — заслуга множества написанных под него пакетов разного назначения. Но если лисп как язык лично для вас не страшен, то выбор между условным numpy/pandas/etc и условным reduce уже будет далеко не таким очевидным. Ну т.е. если считать, что у нас есть два языка, и у обоих есть хороший пакет для решения нашей задачи — то решение уже логично основывать на качестве пакета, а не на языке.

P.S. Поэтому и не раскрыто, мне кажется — автор рассматривает сферический лисп в вакууме, а не задачи, которые на нем хорошо решать.
Основная сущность всех программ — это функция
Уже с этой фразы может начаться эпичнейший холивар.
Да зачем холиварить с заведомо ложным утверждением?
Круто. Как будто ты на берегу моря, в беседке, прохладный ветерок, вискарик и полное парение в стороне от регистров CPU, шаговых двигателей, гироскопических управляющих органов спутника-фотографа, винтов квадракоптера. Нирвана полная!!!, наслаждение полетом…
А какая религия мешает на Lisp использовать регистры cpu и крутить шаговые моторы? Common Lisp позволяет пользователям определять произвольные intrinsic-и. NASA, кстати, использовала Lisp на спутниках для планирования миссии и проверки состояния системы с автоматическим восстановлением. А MIT Scheme используется для управления большим массивом радиотелескопов: крутит моторы и наводит тарелки.

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

"доступными в библиотеках" остались программы на PL1, Algol, Fortran и многих других языках. Человечество любит постоянно все переписывать на разные языки попутно теряя знания. И я полностью согласен с вами в том, что мир подвержен эйфории юнных поколений, которые создают новое только по тому, что не в состоянии понять старое.

Иногда, чтобы понять что-то, нужно самому это сделать. Люди так учатся, поэтому переписывание — это нормально, как и поиск новых языков. Молодёжь, вполне естественно, стремится найти новые ниши для себя, в том числе и языковые. А так… Код на Fortran и Lisp используется в научных вычислениях. PL1 и Algol не видел на практике, а с программами на Fortran и Lisp работать приходится регулярно.

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


Для этих задач необходима поддержка ООП (чтобы можно было описать множество сложных структур данных, в приложениях бывают сотни объектов с десятками полей), взаимодействие с БД через ORM (чтобы не писать рутинные запросы), использование внешних REST API. Характерны большие объемы кода, начиная от 100К строк и выше. Нужны фреймворки, чтобы не изобретать свой велосипед с нуля. Нужны классы, чтобы объединить данные и функции для работы с ними. В Лиспе, как я понимаю, ООП есть, но со страшным синтаксисом (как и сам Лисп). Статической типизации нету, что затрудняет разбор кода. То есть ты видишь функцию и не понять, что ей приходит на вход. Как это узнать?


Есть ли для LISP IDE с поддержкой перехода к функции/методу по клику? Если есть, как они работают без типизации в коде? Что-то я сомневаюсь, что это возможно.


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


Более того, в статье намекают на возможность переопределить стандартные функции вроде +. Вот представьте, перед вами проект на 100К строк и где-то в нем спрятано переопределение функции +. Как вы об этом узнаете и как найдете это место? Это же кошмар какой-то.


Рекурсия бывает нужна (обход деревьев), но за это приходится платить усложнением понимания кода. Код легко понять, если ты сам его написал, а если это кто-то другой засунул рекурсию с 5 аргументами в код из 1000 строк? А? Если можно обойтись без рекурсии — лучше обойтись без нее.


То ли дело PHP — в нем типизация есть, видно, что именно передается в функцию, и синтаксис привычный, со скобочками. Все читабельно, можно кликом перейти к определению функции. Если у вас мощный компьютер и IDE, то можно найти все места использования класса или функции, переименовать класс или функцию (вместе с местами, где он вызывается). При написании кода вам выдаются подсказки, какие переменные, классы, методы доступны. В сравнении с этим программирование на LISP выглядит как каменный век.


Для сравнения синтаксиса — вот пример изменения поля объекта отсюда на LISP и его аналог на PHP:


(setf (slot-value me 'name) "nitro_idiot")
$me->setName("nitro_idiot")

Такое ощущение, что автор на Лиспе пишет задачки вычисления чисел Фибоначчи на 100 строк в одно лицо (то есть знает наизусть весь код). Ему бы реальные проекты поделать.

UFO just landed and posted this here
>А мне как раз о рекурсии рассуждать проще, чем о циклах.
Мне кажется, тут все еще проще. Есть структуры данных, скажем так, плоские (одномерные, или фиксированной размерности) — массивы например. А есть рекурсивные по определению, скажем деревья. Ну или графы. И рассуждать в терминах циклов о деревьях — такое себе развлечение.

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

Объём кода в 100k для программы на Lisp — это очень много. Они обычно гораздо короче программ со схожей функциональностью на других языках. Можете посмотреть на Rosetta Code, например. 120k — это объём операционной системы с драйверами, приложениями, парой игрушек (Doom, Quake), компилятором и текстовым редактором: github.com/froggey/Mezzano

C Lisp-кодом обычно работают в режиме живого редактирования, через REPL. В качестве IDE достаточно использовать Emacs или ViM с плагинами, которые через REPL взаимодействуют с запущенной программой. Сам runtime и рассказывает текстовому редактору, какие функции в нём есть, какие параметры они принимают (потому что, обычно, функции снабжают online-документацией), подсказывает места объявления этих функций и обеспечивает быстрый переход к их исходным текстам. Редактируется всё вполне комфортно. И для этого не нужны типы.

Типы особо не нужны ещё и по той причине, что библиотеки в Lisp не оперируют какими-то сложными иерархиями объектов, обычно взаимодействие осуществляется через простые встроенные типы данных: списки, вектора, числа, процедуры. Этого в большинстве случаев хватает. Изредка используют объекты, но когда есть полноценные замыкания, объекты оказываются нужными не так уж и часто.

Lisp-ы хорошо приспособлены к работе с большими проектами. Там очень мощные средства отладки, и легко можно автоматизировать поиск всех мест, где вызывается процедура. В большинстве случаев эта функциональность уже реализована в IDE. Но довольно просто это сделать и самому: просто навешиваете trace на процедуру и смотрите, откуда и с какими аргументами она вызывается. Гораздо удобнее и практичнее, чем искать это всё в мёртвом коде.

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

Большие проекты на Lisp существуют и вполне успешно развиваются. Первое, что приходит на ум: Guix, TeXmacs, Maxima, ACL2, Mezzano, Grammarly, OpusModus и т.д.
Типы особо не нужны ещё и по той причине, что библиотеки в Lisp не оперируют какими-то сложными иерархиями объектов, обычно взаимодействие осуществляется через простые встроенные типы данных: списки, вектора, числа, процедуры. Этого в большинстве случаев хватает.


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

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

Но довольно просто это сделать и самому: просто навешиваете trace на процедуру и смотрите, откуда и с какими аргументами она вызывается.

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


Это довольно сложно объяснить, наверное, получится криво, но я попробую. Я сам до Scheme активно программировал на С, потом на Go, некоторое время на Haskell. Моим первым всегда был позыв начать со структур данных, а не с кода процедур.

Но на Scheme программирование другое. Вроде как, ты постоянно упрощаешь код. Это следствие обилия скобочек: никому не нравятся скобочки, все хотят упрощать структуру кода. И это автоматическая активность, не надо себя заставлять.

Переписывать код на Scheme очень просто: от редактора требуется умение выделять код от одной скобки до другой, и всё: можно оперировать целыми выражениями при редактировании. Это проще, чем в Си или Haskell. Поэтому мозг активно этим переписыванием пользуется. И от этого постепенно код упрощается, структуры данных упрощаются, во многих случаях становится ясно, что для эффективного решения задачи хватает списка пар. Бывает, накодируешь разных record-ов в модуле, а потом постепенно от них отказываешься, потому что можно гораздо проще.

И постепенно начинаешь программировать по-другому. Начинаешь с кода процедур, с языка понятий на котором описываешь решение задачи, а потом только думаешь о конкретных данных. Вот тут это показано: youtu.be/8lhxIOAfDss

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

Однако на практике так редко делают. Чаще либо работают через generic-процедуры (это аналог type class), либо меняют код так, чтобы он на вход получал замыкания. Замыкания могут содержать любую логику (а не только варианты записей) и обладать простым одинаковым интерфейсом. Нечто вроде data as folds. Замыкания реализованы эффективно, компилятор выполняет lambda lifting, поэтому работает это без особых накладных расходов.

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


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

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

Таков мой опыт. Lisp, как и Haskell, надо пробовать. Это языки, в которых программирование другое на уровне ощущений. Пока не попробуешь, не поймёшь. Просто вчитываться в чужой код не получиться, нужен активный навык программирования. Привычные языки они, в общем-то, все одинаковые по своей структуре. Синтаксис только разный, а идиоматика одинаковая. В Haskell и Lisp сами идиомы другие, поэтому у многих проблемы с чтением кода на этих языках. Дело совсем не в скобках.
>Таков мой опыт. Lisp, как и Haskell, надо пробовать.
Ну я вообще-то пробовал. У меня минимум четыре года практики во вполне промышленной заказной разработке.
Ну, ok. Люди разные, им нравится разное. Кому-то проще на Lisp писать, кому-то проще на Rust, Haskell, C++, NameIt. Я думаю, что языки в не столь отдалённом будущем не будут особо отличаться по гарантиям надёжности кода, эффективности его исполнения или выразительности. Технологии развиваются. Ничто не мешает статически оптимизировать и анализировать динамические языки, а в статические автоматически добавлять рефлексию и инструментализацию. Lisp же останется среди рабочих языков, потому что на нём написаны важные инструменты. Maxima, например,
UFO just landed and posted this here

Причём невыразительном и, скорее всего, unsound.

>Grammarly
Я как-то искал их исходники, и не нашел. Все что у них лежит в гитхабе — это вспомогательная фигня. Было бы неплохо понимать, исходя из чего вы делаете вывод, что это большой проект? Большой в каких попугаях и в сравнении с чем?
Не, почему врут? Я бы сказал, что оценить масштабы, а особенно саму сложность проекта, без вникания в исходники и сравнения с чем-то похожим, довольно проблематично. Поэтому у них есть какой-то взгляд на вещи по итогам их работы, но у нас вполне может быть слегка другой. Или не слегка.

Ну вот взять хоть их пример:
(defun graylog (message &key level backtrace file line-no)
  (let ((msg (salza2:compress-data
              (babel:string-to-octets
               (json:encode-json-to-string #{
                 :version "1.0"
                 :facility "lisp"
                 :host *hostname*
                 :|short_message| message
                 :|full_message| backtrace
                 :timestamp (local-time:timestamp-to-unix (local-time:now))
                 :level level
                 :file file
                 :line line-no
                })
               :encoding :utf-8)
              'salza2:zlib-compressor)))
    (usocket:socket-send (usocket:socket-connect
                          *graylog-host* *graylog-port*
                          :protocol :datagram :element-type '(unsigned-byte 8))
                         msg (length msg))))

Они этим ну не то чтобы хвастают, но все же считают успешным, раз в пост вставили. А по мне — так у меня подобного кода на любом языке найдется похожий пример, и оно будет ничуть не хуже. И примерно того же размера, и того же уровня понимания сторонним человеком. Ну т.е. код как код, ничего такого в нем нет, вообще.
Так, наверное. Кто ж спорит? Я возражал только тому, что Lisp пора сдавать в архив. Нормальный рабочий инструмент с кучей полезных возможностей. Они там дальше в тексте живописуют, как через SLIME занимались удалённой отладкой на живой системе. Клёва же. Это интересная возможность.
Ну так да, я против применимости тоже ничего не имею. Нужно просто фетиш не устраивать из лиспа, и применять по мере необходимости.

> удалённой отладкой на живой системе
Ну, я этим тоже занимался. Но если честно, за такое лучше надавать по рукам :) чтоб неповадно было.
Вообще-то нет, давать по рукам не стоит.

Именно потому что на лиспе (в общем случае) это безопасно — так можно делать.

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

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

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

Поэтому, системы, где крутятся например деньги, или отвечающие за безопасность, именно поэтому и принятно внедрять таким образом, чтобы оставались следы — чтобы было видно, что в такое-то время, установили изменения (ФИО, IP), и эти изменения можно было бы отследить в идеале до задачи в JIRA, на основе которых их внесли, и до коммита.

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

Но я могу зайти на боевой сервер через REPL, создать временный пакет, написать пару аналитических функций, подключить их через хуки к работающей системе, собрать информацию, сдампить ее для алгоритмов машинного обучения, которые будут работать в оффлайне, и удалить временный пакет. И все это — без прогона по пайплайну деплоя, задержек, бюрократии — просто в режиме реального времени. Это может быть очень удобным
Ну я это и в Java могу, и много раз делал. Так что это не достоинство собственно лиспа, строго говоря. И вопрос опасности или безопасности этого процесса определяется скорее тем, есть ли у вас там команда Undo. А если нет — то лучше все же давать по рукам :) Или еще проще — кто сломал, тот и чинит.
Я не знаю и хочу уточнить — могу ли я в Java на множестве живых объектов какого-то класса переопределить метод, не потеряв данные, таким способом, чтобы по мере обращений к каждому объекту старый метод был заменен на новый?
Скажем так — не все изменения возможны. Нельзя менять сигнатуру существующего класса, если упростить. Ну т.е. вы всегда сможете создать что-то новое, но далеко не всегда изменить существующее. Эти ограничения ослаблялись со временем, но они есть.

Ну и в общем понятно, откуда ноги растут — если вы изменили существующий класс несовместимым образом, как отреагируют другие классы, которые от него зависят? Это вопрос без очевидного ответа. В вот менять код внутри методов — на здоровье.
А вот в Common Lisp можно, что создает инженерные удобства. Там все эти проблемы решены на уровне метаобъектного протокола. Это сильно упрощает жизнь
C Lisp-кодом обычно работают в режиме живого редактирования, через REPL.
Основной плюс статической типизации — возможность поменять сигнатуру и получить список всех мест, где нужно что-то поменять. Есть ли такая возможность у лиспа, без необходимости покрывать тестами каждую строку?
Нет, но есть кое-что получше — возможность спросить у работающей программы, где все эти места, в которых нужно что-то поменять
UFO just landed and posted this here
С чего вдруг проблема останова? Интроспекция на уровне структур данных и CFG плюс удобные инструменты — все то же самое, что обычные программисты делают в IDE, только в рантайме, на живой системе.
UFO just landed and posted this here
Сценарий, как я понимаю такой:
— Пишем doFoo которая принимает аргументы
— Пишем main который вызывает doFoo.
— Исправляем doFoo, теперь она не принимает аргументов. Если в этот момент main будет вызыван — будет runtime-error.
— Поэтому перед тем как обновить doFoo следует найти все его вызов doFoo и исправить их, чтобы они не передавали аргументов. Это можно сделать интроспекцией или просто поиском по сорцам.

Но изначально вопрос был про объекты. Впрочем, я вроде бы уже ответил там.
Если какая-то ветка не исполнилась, то о ней будет сообщено? Нужен пример.
def a(b)
	b.с()
end
Вот есть код, который работает, например при открытии какого-то меню, и в функцию передаётся объект с методом c. Вот я произвожу рефакторинг и решаю удалить этот метод. Меню это я не открываю, код не исполняется, могу ли я как-то узнать об этой ошибке не нажимая на кнопку и не покрывая каждую функцию тестами(нужно ведь найти каждый вызов a и убедиться в том, что туда передаётся правильный объект).
> могу ли я как-то узнать об этой ошибке не нажимая на кнопку и не покрывая каждую функцию тестами

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

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

class B
	# begin
	def b
	end
	# end
end

def a(b)
	b.b()
end
Вот полный код примера. Если я удалю код между begin и end, то сигнатура a не изменится, и как следствие, ошибку я поймаю либо в рантайме, либо во время компиляции.
Нет, это не писать типизацию самому, это интроспекция + паттерн-матчинг. Сравнительно немного кода, кстати.

Что касается примера:

При работающем код-волкере ошибка будет при попытке залить def a(b), потому что интроспекция обнаружит отсутствие метода b у класса B.

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

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

Это не compile-time и не run-time — а update-time. Ошибка в рантайме может случиться только если вызываемый метод, например b, вычисляется динамически, такое код-волкер не отловит (если его специально не неписали настолько умным). Но если вы вычисляете вызываемый метод, то код вычисления может сам проверить, есть ли то, что он собирается вызывать, благо метаобьектный протокол позволяет заглянуть.
Это не compile-time и не run-time — а update-time.
Хорошо, допустим есть такой код.

class B
	# begin
	def b
		puts 123
	end
	# end
end

def a(b)
	b.b()
end

def c
	b = B.new()
	a(b)
end

c()
Правильно ли я понимаю, что так-как он выполнился, то среда исполнения знает, что у класса B должен быть метод b, и если я попытаюсь отредактировать этот код, то сразу же получу предупреждение? Но в то же время, если я перезагружу процесс, или передам файлы на другой компьютер, где функция c не вызывалась, то там среда исполнения ничего не сможет сказать? Если да, то как с этим бороться, если нет, то за счёт чего это происходит?
И да, для каких языков это актуально: common|emacs lisp, scheme, racket, clojure?
Редактировать можно сколько угодно, предупреждение можно получить при попытке обновить. Это не зависит от того где и когда будет загружено — можно передавать файлы сколько угодно — среда исполнения узнает о методах классов по мере загрузки кода, который их обновляет.

Это происходит за счет метаобьектного протокола (MOP) Common Lisp
Довольно сложно понять, как это работает. Как можно найти hello world по данному вопросу?
Есть такой hello-world, называется Art of MetaObject Protocol. Только в нем страниц много
То ли дело PHP — в нем типизация есть, видно, что именно передается в функцию, и синтаксис привычный
Как обычно я пошел, взял обычный порошок. Синтаксис привычный — результат отличный!
Правда ли что полноценный REPL есть только у Лиспа? По идее, если ввести, например, в консоль js что-то вроде
var a = 5; console.log(a+b)

то будет ошибка, а Лисп всего лишь скажет что надо бы кое-что добавить.
Что вы называете полноценным? Был такой язык — Аналитик, на машине МИР-2, так он позволял и то, что вы делаете в js — т.е. можно было вывести a+b, если b еще не определена. И получилось бы у вас 5+b. А когда позже вы определите и b тоже, получите например числовой результат. А если определите b как c+d, то увидите 5+c+d. Короче, вариантов масса, и я бы не сказал бы, что лисповский прям самый лучший или мощный.
Не знаю насколько это «полноценный» в вашем смысле, но лисповый репл позволяет отправлять лисп-системе выделенный фрагмент прямо из любого файла и получать применимый результат. Сказать что это удобно — ничего не сказать
Человек спросил про log(a+b). Ну так вот, Аналитик позволял вывести это выражение, даже если часть переменных неизвестна. А потом доопределить. Это разные взгляды на полноценность. И да, это был 1968 год.
UFO just landed and posted this here
Кажется, вы говорите про каррирование и частичное применение. Вот haskell
a b c = b + c

d = a 5 -- вызов с одним аргументом, это ещё не число, но его можно куда-то положить

f = d 4 -- а теперь значение
А вот js, не такой красивый
a = (b) => (c) => b + c

d = a(5)

console.log(d(5))
А вот мы меняем порядок аргументов
a = (b) => (c) => b / c

d = (e, f) => (g) => e(g)(f)

console.log(d(a, 2)(4)) // 2
Only those users with full accounts are able to leave comments. Log in, please.

Articles