Можно ли использовать/реализовать молчаливое программирование (также называемое программированием без ограничений) в Lisp? И в случае, если да, было ли это сделано?
Нечеткое программирование в Lisp
Ответ 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.