Кажется, только lisp позволяет использовать переменную без ее определения в первую очередь? - PullRequest
0 голосов
/ 02 июля 2019

Я обнаружил, что, кажется, только в lisp может определить это:

(lambda (x)(+ x y 1))

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

поэтому в lisp есть понятие «свободная переменная», «связанная переменная». Есть ли у этого языка такая концепция? Потому что все переменные должны определять первыми, поэтому вся переменная является «связанной переменной»? Переменная имеет начальное значение, в глобальном или другом объеме?

Я думаю, что лямбда-концепция является ранней, но если какую-то переменную или значение нужно сначала определить, а затем использовать, не проще ли ее понять и использовать?

Спасибо!

Ответы [ 3 ]

4 голосов
/ 03 июля 2019

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

В этом ответе я обращаюсь только ко второй части, о предварительных ссылках, которые, как мне кажется, прямо вас смущают. Я приведу примеры использования Racket с языком r5rs, поскольку именно это вы использовали в комментарии.

Прежде всего, любой язык, который поддерживает рекурсию без скачков через экстремальные значения ( aka с использованием Y-комбинатора), должен поддерживать ссылки на имена, которые еще не связаны, которые называются прямыми ссылками . Учтите это:

#lang r5rs

(define foo
  (lambda (x)
    (if (null? x)
        0
        (+ 1 (foo (cdr x))))))

ОК, поэтому при оценке функции имеется ссылка на foo для рекурсивного вызова. Но foo еще не связан в этой точке , потому что foo будет привязан к самой функции, и это то, что мы определяем. И действительно, если вы делаете что-то вроде этого:

(let ((r (lambda (x)
           (if (null? x)
               0
               (+ 1 (r (cdr x)))))))
  r)

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

Ну, вы можете утверждать, что letrec превращается во что-то вроде этого:

(let ((r #f))
  (set! r (lambda (x)
            (if (null? x)
                0
                (+ 1 (r (cdr x))))))
  r)

И, возможно, это так. Но как насчет этого?

#lang r5rs

(define foo
  (lambda (x)
    (if (null? x)
        0
        (bar x))))

(define bar
  (lambda (x)
    (+ 1 (foo (cdr x)))))

И для более сложных программ.

Так что здесь есть общая проблема с прямой ссылкой. По сути, невозможно писать программы таким образом, чтобы в тексте программы не было ссылок на имена, о которых пока ничего не известно. Обратите внимание, что в приведенной выше программе foo относится к bar , а bar относится к foo, поэтому нет порядка определений foo и bar который не включает ссылки на имена, которые еще не связаны в источнике.

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

Итак, как это решается? Ну, ответ «это зависит от того, как определен конкретный язык, который вам нужен». Я не совсем уверен, что спецификация R5RS говорит об этом, но я думаю, что я уверен, что Ракет говорит об этом.

Ракет говорит, что все прямые ссылки должны быть отсортированы на уровне модуля. Так что, если у меня есть исходный файл Racket, например:

#lang r5rs

(define a
  (lambda (x) (+ x y)))

(define y 1)

Это нормально, потому что в конце модуля определены и a и x, и поэтому определение a подходит. Это ничем не отличается от определений foo и bar, приведенных выше, и обратите внимание, что исходный файл выглядит так:

#lang r5rs

(define foo
  (lambda (x)
    (if (null? x)
        0
        (bar x))))

не является законным: bar не связано:

$ racket ts.rkt
ts.rkt:7:9: bar: unbound identifier
[...]

Таким образом, ответ на вопрос о прямой ссылке является двояким:

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

Я думаю, это то, что вас смущает. В частности модуль Racket, который содержит только это:

#lang r5rs

(define a (lambda (x) (+ x y)))

Недопустимо в Racket. Но этот модуль:

#lang r5rs

(define a (lambda (x) (+ x y)))
(define y 1)

Допустимо, потому что y связан в момент разрешения прямых ссылок.


Common Lisp более сложен, чем этот, по двум причинам:

  • это Lisp-2, поэтому привязки функций и переменных находятся в разных пространствах имен;
  • нет стандартных привязок лексических переменных верхнего уровня.

Так, например, если мы возьмем что-то вроде этого (предполагается, что оно находится на верхнем уровне, без предшествующих ему других пользовательских определений):

(defun foo (x)
  (+ x y))

Тогда y не может быть ссылкой на лексическую переменную, потому что лексическое окружение foo пусто, поскольку нет привязок лексической переменной верхнего уровня.

Таким образом, y должен быть ссылкой на динамическую переменную (специальная переменная на языке CL). Но на самом деле CL решает проблему прямой ссылки для динамических переменных, говоря, что такие ссылки не разрешены. Таким образом, приведенное выше определение не является законным CL. Многие компиляторы скомпилируют его с предупреждением, но это не обязательно.

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

(defvar *y* 1)

(defun foo (x)
  (+ x *y*))

Это хорошо, потому что *y* известно до определения foo.

И на самом деле я солгал: прямые ссылки на динамические переменные разрешены, но вы должны сказать о них язык:

(defun foo (x)
  (declare (special *y*))
  (+ x *y*))

И теперь, если есть более поздний (defvar *y* ...) (или эквивалентный), вызов foo будет в порядке. Если такого глобально-специального провозглашения *y* не существует, то я думаю, что произойдет, если вызов foo будет либо неопределенным, либо ошибкой (и я надеюсь, что последнее, но я не уверен.

Наконец, даже без глобального специального объявления для *y*, это также хорошо:

(let ((*y* 3))
  (declare (special *y*))
  (foo 1))

Это нормально, потому что *y* локально объявлен специальным (для него есть связанное объявление).

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

3 голосов
/ 02 июля 2019

В Common Lisp точные последствия использования неопределенных переменных не определены.

Общие компиляторы Lisp предупредят:

* (lambda (x) (+ x y 1))

; in: LAMBDA (X)
;     (+ X Y 1)
; --> + 
; ==>
;   (+ X Y)
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::Y
; 
; compilation unit finished
;   Undefined variable:
;     Y
;   caught 1 WARNING condition
#<FUNCTION (LAMBDA (X)) {226A95BB}>
1 голос
/ 05 июля 2019

Есть несколько исключений из правила определения в первую очередь, и для всех них они определяются при упоминании некоторого стандартного значения.Например.JavaScript использует undefined, perl использует undef и т. Д.

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

(define test 
  ((lambda (a) 
     (lambda (b) 
       (list g a b)))
    5))
(define g 0)
(test 10)

То есть во внутренней функции, которая имеет b в качестве связанной переменной.g и a являются свободными переменными, поскольку они не относятся к параметрам этой функции.Это то, что означает свободное, и g не нужно определять перед упоминанием в функциях, но должно существовать до вызова кода, который упоминает.

На лексическом языке с ограничениями 5 будет привязан к a так, что результат будет (0 5 10) в динамическом языке, например.PicoLisp с немного отличающимся синтаксисом для того же самого, ответ будет (0 2 10), поскольку привязка a при создании test выходит за пределы области действия, как только завершится код внутри него.

Почти все языки имеют лексическую область.например.все языковые теги OPs (Ocaml, F #, Haskell, C # и Clojure) имеют лексическую область видимости.

...