Понимание скобок на лету - PullRequest
0 голосов
/ 12 апреля 2020

Мне трудно понять синтаксис let против некоторых других утверждений. Например, у «нормального» оператора есть круглые скобки:

(+ 2 2)
$2 = 4   

И все же у оператора let есть два:

(let ((x 2)) (+ x 2))
$3 = 4                                                                                                                         

Почему это так? Мне кажется довольно запутанным вспоминать, сколько скобок ставить вокруг различных элементов.

Ответы [ 2 ]

4 голосов
/ 13 апреля 2020

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

Все такие формы Lisp создают проблему: если элементы представлены в виде плоского списка, возникает двусмысленность: мы не знаем, где заканчивается один список, а начинается другой!

(let <var0> <var1> ... <form0> <form1> ...)

Например, предположим, что у нас было это:

(let (a 1) (b 2) (print a) (list b))

Что такое (print a): является ли переменная print привязанной к a? Или это form0 для оценки?

Следовательно, подобные конструкции Лиспа почти всегда разрабатываются таким образом, что один из двух списков представляет собой один объект или, возможно, оба. Другими словами: одна из этих возможностей:

 (let <var0> <var1> ... (<form0> <form1> ...))

 (let (<var0> <var1> ...) (<form0> <form1> ...))

 (let (<var0> <var1> ...) <form0> <form1> ...)

Традиционный Лисп следовал третьей идее, изложенной выше, при разработке let . Эта идея имеет то преимущество, что к элементам формы легко и эффективно получить доступ в интерпретаторе, компиляторе или любом коде, который обрабатывает код. Учитывая объект L , представляющий синтаксис let , переменные легко извлекаются как (cadr L) , а тело формируется как (cddr L) .

Теперь, в этом выборе дизайна, все еще есть немного свободы дизайна. Переменные могут иметь структуру, аналогичную списку свойств:

(let (a 1 b 2 c 3) ...)

, или они могут быть заключены:

(let ((a 1) (b 2) (c 3)) ...)

Вторая форма традиционная. В диалекте Ar c, разработанном Лиспом Полом Грэмом, появляется прежний синтаксис.

В традиционной форме больше скобок. Тем не менее, он позволяет опустить формы инициализации: то есть, если желаемое начальное значение переменной должно быть nil, вместо записи (a nil), вы можете просто написать a:

;; These two are equivalent:
(let ((a nil) (b nil) (c)) ...) 
(let (a b c) ...)

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

По сути, мы можем рассматривать традиционный let как предназначенный главным образом для связывания простого списка переменных, как в (let (a b c) ...), который по умолчанию nil. Затем этот синтаксис будет расширен для поддержки начальных значений путем необязательной замены переменной var на пару (var init), где init - это выражение, вычисленное для указания его начального значения.

В любом случае, благодаря макросам, вы можете иметь любой синтаксис связывания. В более чем одной программе я видел макрос let1, который связывает только одну переменную и не содержит скобок. Он используется так:

(let1 x 2 (+ x 2))  ->  4

В Common Lisp мы очень легко можем определить let1 следующим образом:

(defmacro let1 (var init &rest body)
  `(let ((,var ,init)) ,@body))

Если мы ограничим let1, чтобы иметь одно- тело формы, затем мы можем написать выражение с навязчиво несколькими круглыми скобками;

(let1 x 2 + x 2)   ->  4

Вот это:

(defmacro let1 (var init &rest form)
  `(let ((,var ,init)) (,@form)))
3 голосов
/ 12 апреля 2020

Помните, что let позволяет связывать несколько переменных. Каждая привязка переменной имеет вид (variable value), и вы собираете все привязки в список. Таким образом, общая форма выглядит как

(let ((var1 value1)
      (var2 value2)
      (var3 value3)
      ...)
  body)

. Поэтому вокруг x 2 есть две круглые скобки - внутренние скобки предназначены для этой конкретной c привязки, внешние скобки - для списка всех привязок. Это только сбивает с толку, потому что вы связываете только одну переменную, это становится понятнее с несколькими переменными.

...