2015-07-23 2 views
4

Когда я пытаюсь программировать в функциональном стиле с неизменяемыми объектами, последовательные операции в конечном итоге записывается наизнанку, как это:Последовательные процедуры в Лиспе

(thing-operation3 
    (thing-operation2 
    (thing-operation1 thing extra-arg1) 
    extra-arg2) 
    extra-arg3) 

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

((compose1 
    (curryr thing-operation3 extra-arg3) 
    (curryr thing-operation2 extra-arg2) 
    (curryr thing-operation1 extra-arg1)) 
thing) 

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

объектно-ориентированный стиль так гораздо легче читать:

thing.operation1(extra-arg1).operation2(extra-arg2) 
    .operation3(extra-arg3) 

Он читает в естественном порядке, и она также может быть реализован с неизменяемыми объектами.

Что такое ideomatic способ записи таких последовательных операций в Lisp, чтобы их было легко читать?

ответ

10

обычным способом в Common Lisp будет использовать LET*

(let* ((thing1 (thing-operation0 thing0 extra-arg0)) 
     (thing2 (thing-operation1 thing1 extra-arg1)) 
     (thing3 (thing-operation2 thing2 extra-arg2))) 
    (thing-operation3 thing3 extra-arg3)) 

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

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

(pipe 
(thing-operation1 thing extra-arg1) 
(thing-operation2 _2 extra-arg2) 
(thing-operation3 _3 extra-arg3) 
(thing-operation4 _4 extra-arg4)) 

Некоторые язык предоставлять подобные макросы и Lisp библиотеки могут обеспечить вариации этого.Давайте напишем простую версию этого:

(defmacro pipe (expression &rest expressions) 
    (if (null expressions) 
     expression 
    (destructuring-bind ((fn arg &rest args) &rest more-expressions) 
     expressions 
     (declare (ignorable arg)) 
     `(pipe 
     (,fn ,expression ,@args) 
     ,@more-expressions)))) 

Для выше pipe выражения следующий код производится:

(THING-OPERATION4 
(THING-OPERATION3 
    (THING-OPERATION2 
    (THING-OPERATION1 THING EXTRA-ARG1) 
    EXTRA-ARG2) 
    EXTRA-ARG3) 
EXTRA-ARG4) 

Вариант:

(defmacro pipe (expression &rest expressions) 
    (if (null expressions) 
     expression 
    (destructuring-bind ((fn arg &rest args) &rest more-expressions) 
     expressions 
     `(pipe 
     (let ((,arg ,expression)) 
      (,fn ,arg ,@args)) 
     ,@more-expressions)))) 

Это позволит вам написать:

(pipe (+ 1000 pi) 
     (+ arg1 arg1)   ; use the previous result multiple times 
     (+ arg2 (sqrt arg2))) ; use the previous result multiple times 
+1

Существует библиотека под названием 'стрелки', которая реализует такие вещи, доступные из Quicklisp. – Svante

0

Вы можете использовать специальную форму Common Lisp.

Или вы можете определить свой собственный макрос Lisp, чтобы он соответствовал вашему вкусу.

+0

Спасибо. Возможно, вопрос был неясен, каждая операция принимает «вещь» и некоторые дополнительные аргументы и возвращает новую «вещь», которую должна использовать следующая операция. Я хочу связать эти операции ясным и не слишком подробным образом. – alols

3

Clojure имеет оператор поточной, ->, который делает то, что вы ожидаете:

(-> thing 
    (thing-operation1 extra-arg1) 
    (thing-operation2 extra-arg2) 
    (thing-operation3 extra-arg3)) 

Вы можете осуществить это легко, как макрос в других Лисп диалектов. Библиотека Greg Hendershott's rackjure имеет форму ~>, которая делает то же самое в Racket, например.

-> (или ~> in rackjure) macro сращивает результат как первый аргумент каждой подформы. Если вы хотите объединить результат в качестве последнего аргумента, то есть макрос ->> (~>> в стойке).

+1

Библиотека 'стрелки' реализует это аналогично для Common Lisp. – Svante

0

, как о

(reduce (lambda (a b) (funcall b a)) 
    (list thing 
      (partial-apply op1 arg1) 
      (partial-apply op2 arg2) 
      ... 
      (partial-apply opn argn))) 

Common Lisp). В Racket,

(foldl (lambda (a b) (a b)) 
    thing (list 
      (partial-apply op1 arg1) 
      (partial-apply op2 arg2) 
      ... 
      (partial-apply opn argn))) 

Что касается терминологии, это либо ((curry fun) arg) или (partial-apply fun arg).

Смежные вопросы