Почему нет деструкции в деф форме?

В форме let (здесь Clojure) я могу сделать что-то вроде

(let [[u s v] (svd A)] 
   (do-something-with u v))

где svd возвращает список длины три. Это очень естественно, так почему же у нас нет

(def [u s v] (svd A))

и его различные обобщения как поведение формы def по умолчанию? Я не понимаю, как это может помешать тому, что def уже делает. Может ли кто-нибудь, кто понимает дзен Lisp или Clojure, объяснить, почему def не поддерживает привязку (с деструктурированием) столь же мощную, как let?


person Gabriel Mitchell    schedule 19.11.2011    source источник


Ответы (4)


def — это особая форма на уровне компилятора: она создает Var. def должно быть доступно и пригодно для использования до того, как будет доступна деструктуризация. Вы видите нечто подобное с let*, примитивом компилятора, который не поддерживает деструктуризацию: затем, после нескольких тысяч строк в clojure/core.clj, язык, наконец, становится достаточно мощным, чтобы предоставить версию let с деструктуризацией в качестве макроса поверх let*.

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

person amalloy    schedule 19.11.2011
comment
Я думаю, что это тот ответ, который меня интересовал. Рискуя чрезмерной редакции с моей стороны, я думаю, вы говорите, что причина, по которой это не делается в Clojure, частично техническая (в том, что def оказывается примитивом компилятора) , и частично по соглашению (в этом (например, Рич Хикки) мог начать с примитива def* и объявить def позже в какой-то момент в ядре). - person Gabriel Mitchell; 20.11.2011
comment
@GabrielMitchell да, это было бы возможно. Но это гораздо менее полезно для def, чем для let, и в нем будет отсутствовать симметрия. let всегда берет вектор и деструктурирует его внутри; заставить def сделать это сделало бы его гораздо менее удобным, а заставить def принимать либо символ, либо деструктурирующую форму, это довольно ужасно IMO. - person amalloy; 21.11.2011
comment
Не могли бы вы рассказать немного больше о том, почему такой макрос был бы грубым? Прямо сейчас я думаю, что это отличная идея, но, основываясь на вашем комментарии, мне также интересно, не будет ли она как-то противоречить сути Clojure. Обычно лучше следовать сути языка, чем создавать что-то, что идет вразрез с ней, но кажется привлекательным для тех, кто не знаком с языком. Тот факт, что по прошествии 10 лет использование такого макроса не является обычным явлением, еще больше заставляет меня задуматься, не является ли это неуклюжим решением проблемы, которую лучше всего решить другим способом. - person Ben Kovitz; 17.10.2017
comment
Я думаю, что одна из причин заключается в том, что def, как правило, очень простые объекты, свойства которых известны во время компиляции. Например, функции, числа или ключевые слова. Для более крупных и сложных объектов вам обычно не следует вычислять их во время компиляции. Макрос также немного сложно написать связно, потому что (destructure [[x [y]] (foo)]) включает генсиммированные имена для временных векторов, а также для x и y. - person amalloy; 17.10.2017
comment
Я написал макрос def+. stackoverflow.com/a/48998289/345427 Я вижу, как это может быть полезно из repl. Как сказал @amalloy, проблема с gensyms. Есть ли способ определить, что let-var является gensym? - person phreed; 05.03.2018

Это не идеально, но начать нужно с написания def+ https://clojuredocs.org/clojure.core/destructure

(defmacro def+
  "binding => binding-form
  internalizes binding-forms as if by def."
  {:added "1.9", :special-form true, :forms '[(def+ [bindings*])]}
  [& bindings]
  (let [bings (partition 2 (destructure bindings))]
    (sequence cat 
      ['(do) 
       (map (fn [[var value]] `(def ~var ~value)) bings)
       [(mapv (fn [[var _]] (str var)) bings)]])))

С этим можно сделать...

(def+ [u s v] [1 5 9], foo "bar")

...не ставя под угрозу простоту def...

(def+ foo "bar")

... что было запрошено и предложено. Это все еще имеет проблему введения переменных gensym в глобальное пространство имен. Проблема gensym может быть решена, но, учитывая вариант использования (использование в repl), дополнительные переменные, вероятно, приемлемы.

person phreed    schedule 26.02.2018
comment
Я использую это, чтобы помочь мне def строк из let привязок в мой REPL, чтобы затем я мог оценивать подформы let изолированно. Спасибо! - person lxs; 30.05.2019

def в основном является конструктором для Vars. Первый аргумент — это символ, который называет Var. Он берет этот символ и возвращает Var для этого символа. Деструктуризация изменит эту семантику.

Однако вы можете написать макрос, который это делает.

person Chuck    schedule 19.11.2011
comment
Разве то, что вы сказали, не относится и к let? Я имею в виду, нельзя ли использовать тот же аргумент, чтобы сказать, что let не должен поддерживать привязку деструктурирования? - person sepp2k; 20.11.2011
comment
Да, sepp2k поднимает вопрос, который меня интересует. Насколько я понимаю, основная разница между def и let заключается в области привязок, и все же let, похоже, имеет более общее поведение. Как указывает Чак, тот факт, что вы можете написать макрос, полиморфный по первому аргументу (одно поведение для одного символа, другое поведение для списков символов), заставляет меня задаться вопросом, почему это не реализация по умолчанию. - person Gabriel Mitchell; 20.11.2011

Ниже приведены некоторые философские обоснования.

Clojure отдает предпочтение неизменности, а не изменчивости, и все источники изменчивости должны быть тщательно рассмотрены и названы. def создает изменяемые переменные. Идиоматический Clojure, поэтому оба они в любом случае не используют их, а также не хотели бы, чтобы было слишком легко создавать много изменяемых переменных без заботы (например, путем деструктурирования). Однако let и деструктуризация аргументов функций создают неизменяемые привязки, поэтому Clojure упрощает создание таких привязок.

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

person Francis Avila    schedule 27.02.2018