Нечеткое программирование в Lisp - программирование
Подтвердить что ты не робот

Нечеткое программирование в Lisp

Можно ли использовать/реализовать молчаливое программирование (также называемое программированием без ограничений) в Lisp? И в случае, если да, было ли это сделано?

4b9b3361

Ответ 1

Этот стиль программирования возможен в CL в принципе, но, будучи Lisp -2, нужно добавить несколько #' и funcall s. Кроме того, в отличие от Haskell, например, в CL нет функций, и не существует скрытого частичного приложения. В общем, я думаю, что такой стиль не был бы очень идиоматическим CL.

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

(defun partial (function &rest args)
  (lambda (&rest args2) (apply function (append args args2))))

(defun comp (&rest functions)
  (flet ((step (f g) (lambda (x) (funcall f (funcall g x)))))
    (reduce #'step functions :initial-value #'identity)))

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

С помощью чего-то вроде map ((*2) . (+1)) xs в Haskell становится:

CL-USER> (mapcar (comp (partial #'* 2) #'1+) '(1 2 3))
(4 6 8)

Пример sum:

CL-USER> (defparameter *sum* (partial #'reduce #'+))
*SUM*
CL-USER> (funcall *sum* '(1 2 3))
6

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

В Emacs Lisp, кстати, частичное приложение встроено как apply-partially.

В Qi/Shen функции находятся в курсе, и поддерживается неявное частичное приложение (когда функции вызываются с одним аргументом):

(41-) (define comp F G -> (/. X (F (G X))))
comp

(42-) ((comp (* 2) (+ 1)) 1)
4

(43-) (map (comp (* 2) (+ 1)) [1 2 3])
[4 6 8]

В Clojure присутствует также синтаксический сажевый сахар, который дает аналогичное ощущение "конвейерной обработки":

user=> (-> 0 inc (* 2))
2

Ответ 2

Вы можете использовать что-то вроде этого (это немного больше, чем -> в Clojure):

(defmacro -> (obj &rest forms)
  "Similar to the -> macro from clojure, but with a tweak: if there is
  a $ symbol somewhere in the form, the object is not added as the
  first argument to the form, but instead replaces the $ symbol."
  (if forms
      (if (consp (car forms))
          (let* ((first-form (first forms))
                 (other-forms (rest forms))
                 (pos (position '$ first-form)))
            (if pos
                `(-> ,(append (subseq first-form 0 pos)
                              (list obj)
                              (subseq first-form (1+ pos)))
                     ,@other-forms)
                `(-> ,(list* (first first-form) obj (rest first-form))
                     ,@other-forms)))
          `(-> ,(list (car forms) obj)
               ,@(cdr forms)))
      obj))

(вы должны быть осторожны, чтобы также экспортировать символ $ из пакета в который вы размещаете -> - позвоните в этот пакет tacit - и поставьте tacit в предложении use любого пакета, где вы планируете использовать ->, поэтому -> и $ наследуются)

Примеры использования:

(-> "TEST"
    string-downcase
    reverse)

(-> "TEST"
    reverse
    (elt $ 1))

Это больше похоже на F # |> (и на оболочку), чем на Haskell ., но они почти то же самое (я предпочитаю |>, но это вопрос личного вкуса).

Чтобы узнать, что делает ->, просто введите макроэкземпляр последнего примера три раза (в SLIME это выполняется, поместив курсор в первый ( в примере и набрав C-c RET три раза).

Ответ 3

ДА, это возможно, и @danlei уже объяснил очень хорошо. Я собираюсь добавить несколько примеров из книги ANSI Common Lisp Пола Грэма, глава 6.6 о конструкторах функций:

вы можете определить конструктор функций следующим образом:

(defun compose (&rest fns)
  (destructuring-bind (fn1 . rest) (reverse fns)
    #'(lambda (&rest args)
        (reduce #'(lambda (v f) (funcall f v))
                rest
                :initial-value (apply fn1 args)))))

(defun curry (fn &rest args)
  #'(lambda (&rest args2)
      (apply fn (append args args2))))

и используйте его так:

(mapcar (compose #'list #'round #'sqrt)
        '(4 9 16 25))

возвращает

((2) (3) (4) (5))

Вызов функции compose:

(compose #'a #'b #'c)

является эквалентным для

#'(lambda (&rest args) (a (b (apply #'c args))))

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

Сделайте функцию, которая добавляет 3 к аргументу:

(curry #'+ 3)

Подробнее в книге.

Ответ 4

Да, это вообще возможно с правильными функциями. Например, вот пример в Racket, реализующий sum со страницы Википедии:

#lang racket
(define sum (curry foldr + 0))

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