#types #clojure #protocols #code-completion #decoupling
#типы #clojure #протоколы #завершение кода #развязка
Вопрос:
Я обнаружил, что мои приложения clojure очень быстро связываются структурно из-за отсутствия API данных. У меня есть карты с ключами, имена которых, если они введены с ошибкой, вызваны исключениями или ошибками. Я также замечаю, что легко допускать ошибки при деструктурировании списка (например, возможно, вы деструктурируете неправильную часть списка).
Исходя из мира Java, обычно я использую свою среду IDE, чтобы помочь мне получить «правильные» данные из минимальных, неупорядоченных объектов данных. Но передача карты clojure, похоже, является противоположной парадигмой.
Как clojurians защищают код в отсутствие системы типов или завершения кода ide?
Ответ №1:
Напишите функции проверки для ваших «схем» (ключи, а также типы значений и т. Д.), Затем Используйте thm внутри предварительных и последующих условий в вашем коде — поскольку их синтаксис малоизвестен, вот краткое обновление:
(defn foo [x y] ; works with fn too
{:pre [(number? x) (number? y)]
:post [(number? %) (pos? %)]}
( (* x x) (* y y)))
Они зависят от assert
и, следовательно, могут быть отключены. (doc assert)
для получения более подробной информации.
Комментарии:
1. Поскольку это было опубликовано, core.typed был выпущен и также может делать то же самое typedclojure.org
Ответ №2:
Может быть, вы ищете записи?
(require '[clojure.set :as cset])
(defrecord Person [name age address phone email])
;; Make a keyword-based constructor to verify
;; args and decouple ordering.
(let [valid #{:name :age :address :phone :email}]
(defn mk-person[amp; args]
(let [h (apply hash-map args)
invalid (cset/difference (set (keys h)) valid)]
(when-not (empty? invalid)
(throw (IllegalArgumentException. (pr-str invalid))))
; any other argument validation you want here
(Person.
(:name h) (:age h) (:address h) (:phone h) (:email h)))))
=> (def p (mk-person :name "John" :email "john@hotmail.com"))
#:user.Person{:name "John", :age nil, :address nil, :phone nil,
:email "john@hotmail.com"}
Теперь вы можете выбрать, хотите ли вы исключения для имен с ошибками, обращаясь к данным с помощью функций (исключение) или ключевых слов (не исключение).
=> (.fax p)
java.lang.IllegalArgumentException:
No matching field found: fax for class user.Person
=> (:fax p)
nil
Этот подход требует, чтобы вы избегали имен полей, которые могли бы конфликтовать с существующими методами. (См. Комментарий от @Jouni.)
Кроме того, вы можете обойти ограничение имени поля, используя ключевые слова для поиска и функцию доступа, которая проверяет наличие недопустимых ключей:
(defn get-value [k rec]
(let [v (k rec ::not-found)]
(if (= v ::not-found)
(throw (IllegalArgumentException. (pr-str k)))
v)))
=> (get-value :name p)
"John"
=> (get-value :fax p)
IllegalArgumentException: :fax
«Разрушение неправильной части списка» -проблемы с типом могут возникнуть при попытке закодировать что-то вроде «person» в списке; тогда вам нужно запомнить такие вещи, как «почтовый индекс является четвертым элементом в списке «address» на третьей позиции в списке «person»».
В «классическом» Lisp вы можете решить эту проблему, написав функции доступа, в Clojure вы можете использовать записи.
Опечатки вызовут проблемы на любом языке программирования, лучшее, что вы можете сделать, это попытаться выявить их на ранней стадии.
Среда разработки Java с автозаполнением может обнаружить некоторые опечатки, пока вы все еще печатаете, а статически типизированный язык обнаружит многие из них во время компиляции, но на динамическом языке вы не найдете их до времени выполнения. Некоторые люди считают это недостатком динамических языков (включая Python, Ruby и т.д.), Но, учитывая их популярность, многие программисты считают, что полученная гибкость и сохраненный код важнее, чем потеря автозаполнения IDE и ошибок во время компиляции.
Принцип в любом случае один и тот же: более ранние исключения лучше, поскольку требуется меньше кода для поиска причины. В идеале трассировка стека приведет вас прямо к опечатке. В Clojure записи и функции доступа дают вам это.
Комментарии:
1.Однако у вас нет имени поля записи
size
. Вызовы(.size rec)
будут вызывать методsize
, определенный вjava.util.Collection
, и аналогично для всех нулевых методов во всех интерфейсах, которые есть в записях Clojure.2. @Jouni Да, есть 17 имен, которые вы не можете использовать;
size
,count
,values
иmeta
, вероятно, это самые неприятные. Вам придется либо рассматривать их как зарезервированные слова и избегать их использования, либо написать свой собственный API данных. AFAIK.