Почему Лисп допускает замену математических операторов в let? - PullRequest
2 голосов
/ 13 июня 2019

Я знаю в схеме, я могу написать это:

(let ((+ *)) (+ 2 3)) => 6

Как и это, в Clojure:

(let [+ *] (+ 2 3)) => 6

Я знаю, что это может сработать, но это так странно. Я думаю, что на любом языке математические операторы предопределены. C ++ и Scala могут выполнять перегрузку операторов, но это не так.

Разве это не вызывает путаницу? Почему Лисп разрешает это?

Ответы [ 8 ]

7 голосов
/ 13 июня 2019

Это не общая функция Lisp.

В Common Lisp эффекты привязки базовой языковой функции: undefined .Это означает, что разработчик не должен ожидать, что он работает в переносимом коде.Реализация также может сигнализировать о предупреждении или ошибке.

Например, компилятор SBCL сообщит об этой ошибке:

; caught ERROR:
;   Lock on package COMMON-LISP violated when
;   binding + as a local function while
;   in package COMMON-LISP-USER.
;   See also:
;     The SBCL Manual, Node "Package Locks"
;     The ANSI Standard, Section 11.1.2.1.2

;     (DEFUN FOO (X Y)
;       (FLET ((+ (X Y)
;                (* X Y)))
;         (+ X Y)))

У нас может быть свой собственный +в Common Lisp, но тогда он должен быть в другом пакете (= пространство имен символа):

(defpackage "MYLISP"
  (:use "CL")
  (:shadow CL:+))

(in-package "MYLISP")

(defun foo (a b)
  (flet ((+ (x y)
           (* x y)))
    (+ a b)))
6 голосов
/ 13 июня 2019

Отказ от ответственности: это с точки зрения Clojure.

+ - это просто еще одна функция. Вы можете передать его и написать sum с ним, подать частичное заявление, прочитать документы об этом, ...:

user=> (apply + [1 2 3])
6
user=> (reduce + [1 2 3])
6
user=> (map (partial + 10) [1 2 3])
(11 12 13)
user=> `+
clojure.core/+
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'

Таким образом, вы можете иметь много + в разных пространствах имен. Основной по умолчанию получает «use» для вас, но вы можете просто написать свой. Вы можете написать свой собственный DSL:

user=> (defn + [s] (re-pattern (str s "+")))
WARNING: + already refers to: #'clojure.core/+ in namespace: user, being replaced by: #'user/+
#'user/+
user=> (+ "\\d")
#"\d+"
user=> (re-find (+ "\\d") "666")
"666"

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

2 голосов
/ 15 июня 2019

Основные диалекты Lisp не имеют зарезервированных токенов для операций с инфиксами. Нет никакой разницы между +, expt, format или open-file: все они просто символы.

Программа на Лиспе, которая выполняет (let ((+ 3)) ...), духовно очень похожа на программу на С, которая делает что-то вроде { int sqrt = 42; ... }. В стандартной библиотеке C есть функция sqrt, а поскольку C имеет единственное пространство имен (это Lisp-1), то sqrt теперь затенено.

То, что мы не можем сделать в C, это { int + = 42; ...}, потому что + является токеном оператора. Идентификатор вызывается, поэтому существует синтаксическая ошибка. Мы также не можем сделать { struct interface *if = get_interface(...); }, потому что if является зарезервированным ключевым словом, а не идентификатором, даже если оно выглядит как единое целое. Лиспы обычно не имеют зарезервированных ключевых слов, но некоторые диалекты имеют определенные символы или категории символов, которые не могут быть связаны как переменные. В ANSI Common Lisp мы не можем использовать nil или t в качестве переменных. (В частности, те символы nil и t, которые идут из пакета common-lisp). Это раздражает некоторых программистов, потому что им нужна переменная t для «времени» или «типа». Кроме того, символы из пакета ключевых слов, обычно появляющиеся в начале двоеточия, не могут быть связаны как переменные. Причина в том, что все эти символы самооценки. nil, t и символы ключевых слов оцениваются сами по себе, и поэтому не действуют как переменные для обозначения другого значения.

1 голос
/ 14 июня 2019

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

Причина, по которой это разрешено, заключается в том, что в Схеме вы можете встраивать другие языки, а также в другие языки, которые вы хотите использовать *Оператор 1003 * с различной семантикой.

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

(* (+ "abc" "def"))

для представления языка, содержащего такие слова, как этот

empty
abc
abcabc
abcdef
def
defdef
defabc
....

Начиная с основного языка, нетипизированного лямбда-исчисления, можно создать язык, на котором можно переопределить абсолютно все, кроме символа лямбда .Это модель схемы расчета, построенная на.

1 голос
/ 14 июня 2019

В Схеме вы делаете локальную привязку, затеняя все, что выше, с let.Поскольку + и * - это просто переменные, которые просто оцениваются для процедур, вы просто присваиваете старым процедурам другие имена переменных.

(let ((+ *))
  +)
; ==> #<procedure:*> (non standard visualization of a procedure)

В схеме нет зарезервированных слов .Если вы посмотрите на другие языки, список зарезервированных слов довольно высок.Таким образом, в Схеме вы можете сделать это:

(define (test v)
  (define let 10)           ; from here you cannot use let in this scope
  (define define (+ let v)) ; from here you cannot use define to define stuff
  define) ; this is the variable, not the special form
;; here let and define goes out of scope and the special forms are OK again
(define define +) ; from here you cannot use top level define
(define 5 6) 
; ==> 11

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

R6RS делает это еще проще

От R6RS у нас есть библиотеки.Это означает, что мы полностью контролируем, какие формы верхнего уровня мы получаем из стандарта в наши программы.У вас есть несколько способов сделать это:

#!r6rs
(import (rename (except (rnrs base) +) (* +)))

(+ 10 20) 
; ==> 200 

Это тоже нормально.

#!r6rs
(import (except (rnrs base) +))    
(define + *)

(+ 10 20) 
; ==> 200 guaranteed

И наконец:

#!r6rs
(import (rnrs base)) ; imports both * and +   
(define + *)         ; defines + as an alias to *

(+ 10 20) 
; ==> 200 guaranteed

Другие языки тоже это делают:

JavaScript, пожалуй, самый очевидный:

parseFloat = parseInt;
parseFloat("4.5") 
// ==> 4

Но вы не можете трогать их операторов.Они зарезервированы, потому что язык должен сделать много вещей для приоритета оператора.Так же, как Scheme JS - хороший язык для печати утки.

1 голос
/ 13 июня 2019

Это не странно, потому что в lisp нет операторов, кроме функций и специальных форм, таких как let или if, которые могут быть встроены или созданы как макросы.Так что здесь + не является оператором, а функцией, которая назначается символу +, который добавляет свои аргументы (в схеме и в ближайшем будущем вы можете сказать, что это просто переменная, которая содержит функцию для добавления чисел), то же самое * не является оператором умножения, а символом звездочки, который умножает свои аргументы, так что это просто удобная запись, в которой используется символ +, это может быть add или sum, но + короче и аналогично другим языкам.

Это одна из тех концепций, которые извращают ум, когда вы впервые ее нашли, например, функции в качестве аргументов и возвращаемые значения других функций.

Если вы используете очень простые LIPS и лямбда-исчисление, выдаже не нужны числа и операторы + на базовом языке.Вы можете создавать числа из функций и функций плюс и минус, используя один и тот же трюк, и назначать их символам + и - (см. Кодировка Церкви )

1 голос
/ 13 июня 2019

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

лямбда-исчисление - это упрощенная система управления связыванием переменных. В лямбда-исчислении правила для таких вещей, как

(lambda (x) (lambda (y) y))

и

(lambda (x) (lambda (y) x))

и даже

(lambda (x) (lambda (x) x))

тщательно указаны.

В lisp LET можно рассматривать как синтаксический сахар для лямбда-выражения, например, ваше выражение (let ([+ x]) (+ 2 3)) эквивалентно ((lambda (+) (+ 2 3)) x), которое согласно лямбда-исчислению упрощается до (x 2 3).

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

0 голосов
/ 19 июня 2019

Почему Lisp разрешает повторное связывание математических операторов?

  • для согласованности и
  • , потому что это может быть полезно.

Разве это не вызывает путаницу?

  • Нет.

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

LISP склоняют качели в другую сторону, предпочитая последовательность, а не разборчивость.

Согласованность

В Clojure (я знаю Лисп) математические операторы (+, -, *, ...) - ничтоспециальный.

  • Их имена - это обычные символы.
  • Они являются core функциями, как и любые другие.

Так что, конечно, вы можете заменить их.

Полезность

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


Алгебру Clojure труднее читать.

  • Все операторы имеют префикс.
  • Все операторские приложения являются явными - без приоритетов.

Если вы решили использовать инфиксные операторы с приоритетами, выможет сделать это. Incanter делает так: здесь - некоторые примеры, а здесь - исходный код.

...