LINUX.ORG.RU

Моя первая программа на Lisp

 ,


2

5

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

http://pastebin.com/CZ6MTw1S

Это преобразователь из инфиксной в префиксную форму. С учётом скобок и приоритетов операций. А ещё с возможностью вызывать функции помимо выполнения математических операций.

То есть пишем что-то вроде (c-expr ( 2 + 2 * 2 + sin ( 1 ) ) / 2 ), а оно преобразует это в нормальную лисповую форму записи - (/ (+ (+ 2 (* 2 2)) (sin 1)) 2), а такое уже легко вычисляется средствами самого Lisp.

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

Просто в Lisp не принято писать так, как в обычных языках программирования (да и некоторые конструкции при дословном переводе будут некрасиво смотреться), поэтому и советы по оптимизации от них не годятся. Например, если сохранять в локальные переменные все значения, которые используются дважды (чтобы не считать их 2 раза), то будет дикая лапша из let. Или это не нужно? Или это не лапша (и вообще мой код не лучше), а норма?

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

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

futures вызывают синхронизацию потоков на каждый чих, threads не скалируются на ядра.

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

Ну вот, допустим, я нашёл инструкцию и установил slime.

Теперь у меня есть 2 варианта:

1) Я открываю lisp-файл в emacs. При этом появляется менюшка SLIME, однако практически все пункты из неё недоступны. То есть я могу редактировать и сохранять код, но запустить его (а в случае ошибки увидеть на какой строке упало) не могу.

2) Я пишу M-x slime. Открывается интерактивная сессия Lisp. При этом становятся доступны абсолютно все команды из меню Slime. Это, конечно, хорошо, чтобы быстро проверить какую-нибудь функцию, но я таки хочу редактировать свой файл с кодом, периодически его запуская. К счастью, я могу сделать File -> Open и загрузить свой исходник. При этом пункты меню доступными быть не перестают. Но я, по-прежнему, не понимаю, как запустить мою программу.

Авто-комплит работает, да, это уже хорошо.

Что делать? Или, быть может, ты можешь посоветовать какой-нибудь хороший туториал, чтобы там все эти мелочи были расписаны? А то в основном я вижу статьи вида «копируешь мой ~/.emacs» и всё круто (при этом как этим «круто» - пользоваться информации в статье почти нет).

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

В процессе разработки на Lisp запускается не вся программа, а только нужная тебе функция.

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

Как это работает:

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

Соотв. процесс разработки выглядит так:

1) Запускаешь lisp машину - M-x slime

2) Открываешь нужный тебе код и выставляешь курсор на последнюю ) того выражения, которое хочешь исполнить

3) Говоришь emacs'су исполнить это выражение C-x e (или C-x C-e, не помню, я зажимаю Caps Lock и жму x, а потом e)

После чего твой код исполнится.

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

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

То есть мне нужно по очереди исполнить все определения функций, которые есть в моём коде? А если их много и они друг от друга зависят (смысла по 1 функции запускать нет)? Или Emacs автоматически подгрузит все дополнительные функции?

Запустил slime, разделил окно на две части, а во второй части открыл свой исходник (в стартовом посте есть ссылка на pastebin.com). Поставил курсор на конец моего вызова print, сделал C-x C-e (C-x e судя по всему подсказка по аргументам функции, а не запуск) - получил в первом буфере ошибку о том, что функция c-expr не найдена.

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

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

То есть мне нужно по очереди исполнить все определения функций, которые есть в моём коде? А если их много и они друг от друга зависят (смысла по 1 функции запускать нет)? Или Emacs автоматически подгрузит все дополнительные функции?

Не подгрузит. Откуда он узнает какие функции грузить? Ты же можешь, ради тестов, заменить часть функций во время зазработки на фейковые (привет IoC).

Там есть хоткей на подгрузку всего файла (полазай по менюшкам, нет у меня сейчас emacs'а со slime).

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

Не, там есть пакеты. Копай в сторону asdf и quicklisp - сам я в них не лез, т.к. муть редкостная (сделать пакетную систему, в которой не разберёшься за час это надо было ещё постараться), а CL для меня бесполезен и что-то на нём писать я не стал, поэтому и не вкладывался особенно в эти штуки.

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

Что делать?

Запустить slime, открыть нужный файл в emacs. При необходимости посмотреть работу функции скомпилировать ее с помощью C-c C-c (либо M-x slime-compile-file, если функций много), после чего перейти в соседний буфер с slime и запустить функцию.

Я обычно так работал.

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

Можно PCL начать и закончить. Мне хватает PCL и cltl2 (хоть он и справочник, и до стандарта, но написано всё четко и ясно)

anonymous
()

Все везде нахваливают Lisp и я решил тоже потыкать его.

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

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

Всё, что тебе нужно, это время :-) Особо не парься над тем, как правильно писать, над всякой чепухой типа «true lispy-way» :-) Пиши и получай удовольствие :-) И почитай код у здравых лисперов :-) Например, мне нравится код Eitaro Fukamachi :-)

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

Список наглядно показывает всю монструозность языка Common Lisp :-) Для сравнения, всё, чтобы писать на C, достаточно книги K&R размером 300 страниц :-) Тут же 6 толстенных книг всего лишь для того, чтобы познать язык описания действий для железяки :-) Интересно, много ли людей изучили 6 толстенных книг для разговоров между собой? :-)

Это я к тому, что книги учат, конечно, но плохо :-) Лучше учит практика, а не последовательное чтение множества томов сочинений :-)

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

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

Интересен ещетакой факт. Пол Грехем, автор некоторыхиз этих книг, поднялся на том, что они с Моррисом подняли лаве на продаже виавеб. В то время инет магазины были примитивны, при этом конъюктура была просто йфантастически удачной, тогда вовсю раздувался пузырь доткомов, и сметали все подряд проекты. Тогда был проект, типа,поиска дешевых авиабелитов, не помнюкак называется, суть в том, что авиакомпании продавали туда авиабилеты, которые хреново расходились, а компания еще за это доплачивала, короче,помойка, так вот, ее капитализация была больше, чем суммарная капитализация 3-хведущих американскихавиакомпаний!!!:)

Продержи он этот магазин до 2000-го, он бы и за 3 цента бы не ушел. Но Грехем рассказывал своим адептам, что они больны парадоксом блаба:) Потом у него бизнес как то не очень пошел:) Да и с лиспом он завязал, его диалект арк остался в зачаточном состоянии, причем пейсал он его на PLT-scheme. Поматросил и бросил:)

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

portquest2016
()

Например, если сохранять в локальные переменные все значения, которые используются дважды (чтобы не считать их 2 раза), то будет дикая лапша из let. Или это не нужно? Или это не лапша (и вообще мой код не лучше), а норма?

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

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

infixToPrefix = (s) => {
var op = s[0]

Грубая ошибка: название функции должно отражать то, что делает функция, а не вводить в заблуждение.

monk ★★★★★
()

Насколько я рационально её написал

У меня бы получилось так: http://pastebin.com/aVJdPJuk

не будет ли проблем с производительностью

Если писать на стандарте, а не конкретной реализации, то в Common Lisp не обязательна оптимизация хвостового вызова (http://0branch.com/notes/tco-cl.html). В этом случае надо вручную развернуть рекурсию в tagbody.

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

В Racket для многопоточности есть places. Причём при необходимости программа из многопоточной превращается в распределённую заменой places на distributed places.

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

внутри вызова функции создается функция, и тут же вызывается, это п*ц

Временные переменные тоже не нравятся?

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

Временные переменные тоже не нравятся?

Я так понял, он про отсутствие имени у временных переменных/функций. Конструкция (lambda (...) ...).

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

Временные переменные тоже не нравятся?

Я так понял, он про отсутствие имени у временных переменных/функций. Конструкция (lambda (...) ...).

анонiмус - эталон блаб-программиста

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

Но Грехем рассказывал своим адептам, что они больны парадоксом блаба:)

Что такое «парадокс блаба»? :-) А то я в теориях всяких разных не очень силён :-)

Да и с лиспом он завязал, его диалект арк остался в зачаточном состоянии, причем пейсал он его на PLT-scheme. Поматросил и бросил:)

Для тех, кто не в курсе, PLT-scheme сейчас переименован в Racket :-) Честно говоря, я не понимаю этого названия - Racket :-) Причём тут Рэкет? :-)

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

Что такое «парадокс блаба»?

Подробно: http://www.nestor.minsk.by/sr/2003/07/30710.html

Вкратце: то, чего не понимаем, не можем корректно сравнить.

Когда наш гипотетический Блаб-программист смотрит вниз на континуум мощности языков, он знает, что смотрит вниз. Менее мощные, чем Блаб, языки явно менее мощны, так как в них нет некой особенности, к которой привык программист. Но когда он смотрит в другом направлении, вверх, он не осознает, что смотрит вверх. То, что он видит, — это просто «странные» языки. Возможно, он считает их одинаковыми с Блабом по мощности, но со всяческими сложными штучками. Блаба для нашего программиста вполне достаточно, так как он думает на Блабе.

Причём тут Рэкет? :-)

The name Racket meets some basic criteria: it isn't used already, it's easy to pronounce and spell, and it has a vague connection to the word “scheme.”

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

Классно! Научусь простой многопоточке, попробую distributed places. А по твоему опыту, когда есть смысл использовать futures, и когда есть смысл использовать places? Я понял, что они многопоточку реализуют каждая по своему, со своими + и -.

Hertz ★★★★★
()

Теперь можете написать игру, к примеру, что-то очень простое, можно по известным образцам, чтобы не сильно напрягаться... :)

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

Солнышко, зачем тебе кружочек? Ты сама как солнышко... Посмотри на солнышко - вот тебе и кружочек... Только смотри рано утром, на закате или в солнцезащитных очках... :)

anonymous
()

Что-то я не нахожу комментариев в этой программе... Но я Lisp практически не знаю... :)

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

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

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

А по твоему опыту, когда есть смысл использовать futures, и когда есть смысл использовать places?

Вот здесь подробно: https://docs.racket-lang.org/guide/parallelism.html

Фактически, везде где в не-Racket тебе нужен thread, в Racket надо использовать place. future использовать можно для параллелизации коротких операций. Внутри future нельзя аллоцировать память (то есть выполнять любой конструктор) и нельзя менять переменные, доступные извне future. Точнее можно, но выполняться тогда будет не параллельно. С другой стороны, future автоматически раскидается на столько ядер, сколько есть, а place надо вручную запускать.

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

Нет, я не об этом. Это вообще из другой оперы — типа, безымянные ф-ции бла-бла-бла.

я говорю о том, что не следует писать что-то типа:

fu = function(){
   local fu = function(){bla-bla}
   //somecode
   local fu run
} 
когда можно напейййсать типа
some environment{
local names here: fu = function(){bla}
globalfu = function(){bla-bla}
}

казалось бы,самоочевидная вещь? х там, сплошь и рядом это встречается. при каждом выхове вн-няя ф-ция создается заново. Даже «создатели» SICP ЕМНИП,такими вещами грешат.

Дело тут дажене в перформансе, просто это блевотный код

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

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

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

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

сплошь и рядом это встречается. при каждом выхове вн-няя ф-ция создается заново

В Racket не создаётся. Проверял:

(define (foo x)
  (define (bar x) (+ x 1))
  (+ (bar x) 2))
(define (bar x) (+ x 1))

(define (foo x)
  (+ (bar x) 2))

выдают идентичный скомпилированный код. Определение функции уходит в константу.

> Дело тут дажене в перформансе, просто это блевотный код

Если функция небольшая, то глупо пространство имён замусоривать.

Сравни
(define (process1! v x y)
  (define (do-item! x) ...)
  (define (filter-items x) ...)
  (define (prepare-result x) ...)
  (prepare-result (map do-item! (filter filter-items v))))

(define (process2! v x y)
  (define (do-item! x) ...)
  (define (filter-items x) ...)
  (define (prepare-result x) ...)
  (prepare-result (map do-item! (filter filter-items v))))

И

(define (process1-do-item! x) ...)

(define (process1-filter-items x) ...)

(define (process1-prepare-result x) ...)

(define (process1! v x y)
  (process1-prepare-result (map process1-do-item! (filter process1-filter-items v))))

(define (process2-do-item! x) ...)

(define (process2-filter-items x) ...)

(define (process2-prepare-result x) ...)

(define (process2! v x y)
  (process2-prepare-result (map process2-do-item! (filter process2-filter-items v))))

В Common Lisp специально для этих целей сделаны формы flet и labels.

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

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

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

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

У places проблема в том, что они сами по себе тяжелые даже общий байткод от racket/racket-base почему-то не шарится, если только не пофиксили в последние год-два). По логике, конечно, понятно, что предполагается 1 place на ядро, но если бы их можно было быстро спавнить/уничтожать и держать сотнями/тысячами (хотя бы) было бы гораздо лучше.

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

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

То, что «можно было быстро спавнить/уничтожать и держать сотнями/тысячами» называется thread. Если по place на ядро сделал, то дальше кооперативная многозадачность thread лучше работает, чем вытесняющая от ОС (почти все операции с данными бесплатно можно считать атомарными).

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

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

А что в Linux (или Windows) можно сделать тысячу потоков у приложения и ядро не загнётся?

monk ★★★★★
()

молодец. теперь напиши что-нибудь на хаскеле

anonymous
()

Пока книги по лиспу изучал писал все в виме с плагином slimv, кроме пару маленьких неудобств все работало как надо. Очень лень было emacs учить.

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

А что в Linux (или Windows) можно сделать тысячу потоков у приложения и ядро не загнётся?

Речь о юзерспейсных потоках.

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

Речь о юзерспейсных потоках.

Так юзерспейсные в Racket на 5+ : threads. Практически не хуже чем в Erlang.

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

нагло врут. Лимит тредов выставляется в другом месте и имеет существенно большую величину.

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