LINUX.ORG.RU

Почему на Common Lisp пишут в императивном стиле?


0

1

Недавно начал читать PCL (до этого изучал Scheme) - не могу привыкнуть к императивщине. По-моему, Лисп просто создан для того чтобы быть функциональным языком программирования :)

Связано ли это как-нибудь с тем фактом, что CL является «промышленным стандартом» Лиспа?


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

>Если касаться чисто алгоритмической части, то Функциональный стиль затрудняет понимание.

Мой опыт говорит совершенно об обратном. Такие функции лучше воспринимаются мною, чем сложные циклы. Если же говорить шире об алгоритмах вообще, то тут даже сравнения нет с императивным стилем. Получается настолько сжато и лаконично выразить достаточно сложное поведение, которое я бы просто утомился реализовывать без всяких map, filter, лямбд, да разных ускорителей типа for comprehension и computation expressions. Одним словом - декларативность. Впрочем, не стану обобщать свой опыт на всех программистов.

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

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

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

>Одним словом - декларативность.

Ага. А теперь оцени, где больше декларативности:

(defun fact (n)
  (loop :for i :to n
        :for fact = 1 :then (* i fact)
        :finally (return fact)))

(defun fact (n)
  (labels ((iter (n acc)
             (if (> n 0)
               (iter (1- n) (* acc n))
               acc)))
    (iter n 1)))
Love5an
()
Ответ на: комментарий от dave

А в чем проблема? Даже на Питоне можно писать в функциональном стиле, не трогая императивщину вообще. На CL тем более, особенно если его сначала допилить, что благодоря его возможностям к МП не сложно.

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

> Такие функции лучше воспринимаются мною

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

чем сложные циклы.


Не знаю о чём ты, у меня сложных циклов нет. Я всегда считал, что всякие дикие циклы являются следствием неумелого обращения с кодом.

Получается настолько сжато и лаконично выразить достаточно сложное

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


всяких map, filter, лямбд, да разных ускорителей типа for


comprehension и computation expressions.



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

Одним словом - декларативность.


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

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

> Даже на Питоне можно писать в функциональном стиле,

не трогая императивщину вообще.


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

На CL тем более, особенно если его сначала допилить


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

что благодоря его возможностям к МП не сложно.


Это просто миф.

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

Не миф.

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

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

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

только если в этом языке нет TCO, то получится весьма уныло

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

> Тут пора бы давно узаконить в стандарте хвостовую рекурсию

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

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

> Если проект не слишком сложный/большой, обилие макросов

и DSL не дает ощутимых плюсов.


Угу, а если большой, то откровенно вредит.

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

Ага. А теперь оцени, где больше декларативности:

конечно в

fact n = foldl (*) 1 [2..n]
korvin_ ★★★★★
()

кстати, dsl никогда не было сильной стороной cl
тот же факториал на scala выглядит гораздо «dsl'нее»

def factorial(n:Int) = 1 to n product
h1t
()
Ответ на: комментарий от h1t

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

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

кстати, dsl никогда не было сильной стороной cl тот же факториал на scala выглядит гораздо «dsl'нее»

при чём тут dsl? и ты пропустил пример архимага? для таких как ты можно даже функцию product сделать и будет то же самое, типа

(defun product (&key (from 1) (to 1))
  (iter (for i from from to to)
        (multiply i)))

соответственно

(defun fact (n) (product :from 1 :to n))

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

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

Да-да, пытался я на питоне писать в функциональном стиле. Получается задорно и глупо.

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

«Никогда» по отношению к cl рядом со свежим высером на вроде scala выглядит, как минимум, умиляюще.

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

>Как по твоему, насколько ценно мнение о КЛ человека, который про КЛ вообще ничего не знает?

Сдается мне, про лиспы я знаю поболее твоего. И не только про лиспы.

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

А из чего это следует? Интуиция ожидает, что for i:=1 to 0 не выполнится ни разу. Я смотрю, на лиспе код даже нарочно обфусцировать не надо.

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

> А из чего это следует?

из того, что аккумулятор для умножения логично инициировать единицей?

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

> А из чего это следует? Интуиция ожидает, что for i:=1 to 0

не выполнится ни разу.


Действительно, не выполнится. Но для операции multiply нужно, очевидно, два аргумента, так что для её успешного выполнения нужен initial элемент, который по-умолчанию равен 1 и который имеется не зависимо от того, сколько раз выполнялся цикл.

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

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

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

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

Странное утверждение.

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

В Doors я безусловно мог бы писать так:

(defun last-error ()
  (cffi:foreign-funcall ("GetLastError"
                          :convention :stdcall
                          :library kernel32)
    :uint32))

(defun std-handle (handle-type)
  "Retrieves a handle to the specified standard device."
  (declare (type (or (member :input-handle :output-handle :error-handle)
                     (unsined-byte 32))
                 handle-type))
  (let ((rv (cffi:foreign-funcall ("GetStdHandle"
                                    :convention :stdcall
                                    :library kernel32)
              :uint32 (etypecase handle-type
                        ((eql :input-handle) (logand -10 #xFFFFFFFF))
                        ((eql :output-handle) (logand -11 #xFFFFFFFF))
                        ((eql :error-handle) (logand -12 #xFFFFFFFF))
                        ((unsigned-byte 32) handle-type))
              :pointer)))
    (declare (type cffi:foreign-pointer rv))
    (if (cffi:pointer-eq rv (cffi:make-pointer #xFFFFFFFF))
      (error 'windows-error :code (last-error))
      rv)))

(defun write-console (buffer &optional
                             (console-output (std-handle :output-handle))
                             (chars-to-write (length buffer)))
  "Writes a character string to a console screen buffer."
  (declare (type string buffer)
           (type (unsigned-byte 32) chars-to-write)
           (type (or null cffi:foreign-pointer) console-output))
  (let* ((chars-written 0)
         (mapping (babel-encodings:lookup-mapping
                     cffi::*foreign-string-mappings*
                     #+doors.unicode :utf-16le
                     #-doors.unicode :ascii))         
         (buffer-size (funcall (babel-encodings:octet-counter mapping)
                               buffer 0 chars-to-write -1))
         (return-value 0))
    (declare (type (unsigned-byte 32) chars-written buffer-size)
             (type (signed-byte 32) return-value))
    (cffi:with-foreign-object (pchars-written :uint32)
      (cffi:with-foreign-pointer (pbuffer buffer-size)
        (funcall (encoder mapping)
                 buffer 0 chars-to-write pbuffer 0)
        (setf return-value
              (cffi:foreign-funcall (#+doors.unicode "WriteConsoleW"
                                     #-doors.unicode "WriteConsoleA"
                                     :convention :stdcall
                                     :library kernel32)
                :pointer (or console-output (cffi:null-pointer))
                :pointer pbuffer
                :uint32 chars-to-write
                :pointer pchars-written
                :pointer (cffi:null-pointer)
                :int)
              chars-written (cffi:mem-ref pchars-written :uint32))))
    (if (zerop return-value)
      (error 'windows-error :code (last-error))
      chars-written)))
Но учитывая громадный размер Windows API, это было бы еще то извращение.

Поэтому я написал Virgil и теперь пишу так:

(define-external-function ("GetLastError" last-error)
    (:stdcall kernel32)
  (dword))

(defun %invoke-last-error (value)
  (declare (ignore value))
  (error 'windows-error :code (last-error)))

(defalias last-error (type &optional (predicate 'not-null))
  `(filtered ,type ,predicate %invoke-last-error))

(define-enum (std-handle
               (:base-type dword)
               (:conc-name std-))
  (:input-handle (logand #xFFFFFFFF -10))
  (:output-handle (logand #xFFFFFFFF -11))
  (:error-handle (logand #xFFFFFFFF -12)))

(define-external-function
    ("GetStdHandle" std-handle)
    (:stdcall kernel32)
  ((last-error handle valid-handle-p))
  "Retrieves a handle to the specified standard device."
  (std-handle std-handle))

(define-external-function
    (#+doors.unicode "WriteConsoleW"
     #-doors.unicode "WriteConsoleA"
                   write-console)
    (:stdcall kernel32)
  ((last-error bool) rv chars-written)
  "Writes a character string to a console screen buffer."
  (console-output handle :optional (std-handle :output-handle))
  (buffer (& tstring))
  (chars-to-write dword :optional (length buffer))
  (chars-written (& dword :out) :aux)
  (reserved pointer :aux &0))

Вредят, говоришь?

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

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

Это как раз потому, что в коммон лиспе относительно скудные возможности на этот счет (относительно хаскель, например)

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

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

Ты написал много реализаций CL?

Если касаться чисто алгоритмической части, то Функциональный

стиль затрудняет понимание.


Странное утверждение.


Да ну?

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

>> что благодоря его возможностям к МП не сложно.

Это просто миф.

Это точно. Я бы посмотрел на TCO на макросах =) (если предположить, что ни одна реализация лиспа TCO упорно не поддерживает)

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

Это как раз потому, что в коммон лиспе относительно скудные возможности на этот счет (относительно хаскель, например)

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

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

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

Ты написал много реализаций CL?

А ты? =). TCO делается просто. И даже выполняется некоторыми компиляторами С, чего уж говорить про всякие лиспы.

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

>> Это как раз потому, что в коммон лиспе относительно скудные возможности на этот счет (относительно хаскель, например)

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

А я об убогости и не говорил.

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

> Вредят, говоришь?

Конечно, вредят. И не надо тут простыни кода выкладывать, я всё равно вникать не будут.

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

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

Дальше - ещё больше. Если изменился макрос в каком-либо проекте другого разработчика, например, в широко известной библиотеке. То самый простой способ учесть это - удалить вообще все fasl-файлы в системе. И хорошо, если ты знаешь об этом, что что-то изменилось. А если не знаешь - то можешь таких глюков нахватать.

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

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

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

>>> Если касаться чисто алгоритмической части, то Функциональный стиль затрудняет понимание.

Странное утверждение.

Да ну?

Ага, странное. Функциональный стиль очень упрощает чтение и понимание программы.

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

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

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

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

>Кстати, ты небось и FFI'шить ручками собрался, а про swig и не слышал?

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

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

>Если ты мне не веришь, попробуй cl-sql.

cl-sql - это унылая мерзость. Надо смотреть на реально работающие библиотеки для работы с БД. Например, postmodern, cl-sqlite.

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

>Особенно это, самая раздутая сплетня.

Я писал про свои ощущения. Если удается задачу формализовать в терминах функционального подхода — ее лучше (лично мне) становится понятно.

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

> Ага, странное. Функциональный стиль очень упрощает

чтение и понимание программы.


В Common Lisp??? Ну если только речь о факторилах...

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

> Перекомпилировать надо, глюки видите ли(а то в функциях их

не бывает).


Глюк в функции лечится просто перекомпиляцией функции. При изменении макроса надо перекомпилировать весь зависящий от него код. Почувствуй разницу.

Лучше посмотри на код,и оцени практическую полезность

умных макросов.



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

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

>Встречный такой же вопрос про swig. Я его ручками трогал, оно очень мерзкое и противное.

Я его в работе использовал для организации питоновских биндингов к сишной библиотеке. Без проблем заработало.

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

>библиотеки для работы с БД

cl-sqlite

Как-то плохо сочетаются слова «БД» и «sqlite».

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

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

>Но для операции multiply нужно, очевидно, два аргумента, так что для её успешного выполнения нужен initial элемент, который по-умолчанию равен 1 и который имеется не зависимо от того, сколько раз выполнялся цикл.

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

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

>Кстати, SWIG это да, то еще говно.

Да, стахановцы велосипедостроения не одобряют. Вот только эта фраза сразу выдает то, что ничего сложнее int foo(const char *bar) ты из лиспа не вызывал. Повозился бы со сложными типами, сам бы все понял. Ну и с плюсовкой, само собой, без swig'а лучше даже не связываться.

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

>Глюк в функции лечится просто перекомпиляцией функции.
А если функция инлайнится?

Практическую полезность чего-либо нельзя оценить просто посмотрев на это. С этим нужно работать и на практике убеждаться в полезности.


По-моему, на примере вышеприведенного кода вполне ясно, что без макросов Virgil, из Doors получился бы стокилометровый копипаст.

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

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

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

Это точно. Я бы посмотрел на TCO на макросах =)

Посмотри. Сделано за 5 минут в vim'e:


(defvar *trampolining?* t)

(defun tail? (object)
  (and (consp object)
       (eq (car object) 'tail)))

(defun eval-tail (tail)
  (funcall (cdr tail)))

(defmacro tail (form)
  `(cons 'tail 
	 (lambda () 
	   (let ((*trampolining?* nil)) 
	     ,form))))

(defun trampoline (object)
  (loop :while (tail? object)
     :do (setf object (eval-tail object))
     :finally (return object)))

(defun replace-last (list repl-fn)
  (append (subseq list 0 (- (length list) 1))
          (list (funcall repl-fn (car (last list))))))

(defun tco-transform (fn-body)
  (replace-last 
   fn-body 
   (lambda (last) `(tail ,last))))

(defmacro defun-with-tco (name args &body body)
  `(labels ((,name ,args
                   ,@(tco-transform body)))
     (defun ,name ,args
       (if *trampolining?*
         (trampoline 
           (let ((*trampolining?* t))
             (,name ,@args)))
         (let ((*trampolining?* t))
           (,name ,@args))))))

(defun-with-tco ping ()
  (format t "ping~%")
  (pong))

(defun-with-tco pong ()
  (format t "pong~%")
  (ping))

(defun-with-tco factorial (n acc)
  (if (= n 1)
      acc
      (factorial (- n 1) (* n acc))))

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