Как построить надежные данные API в Clojure - PullRequest
9 голосов
/ 07 октября 2011

Привет, ребята: я обнаружил, что мои приложения clojure структурно связаны очень быстро из-за отсутствия API данных ... - У меня есть карты с ключами, имена которых, если они ошибочно набраны, вызывают исключения или ошибки.Я также замечаю, что при деструктурировании списка легко ошибиться (например, может быть, вы деструктурируете не ту часть списка) .....

Исходя из мира Java, обычно я использую свою IDE, чтобы помочья получаю «правильные» данные из минимальных, неупорядоченных объектов данных, но передача карты clojure, по-видимому, противоположна этой парадигме.

Как защитный код clojurians защищает в отсутствие системы типов или ideзавершение кода ...?

Ответы [ 2 ]

5 голосов
/ 07 октября 2011

Напишите функции проверки для ваших «схем» (ключей, а также типов значений и т. Д.), А затем используйте thm внутри пред- и постусловий в вашем коде - поскольку их синтаксис мало известен, это быстрое обновление:

(defn foo [x y] ; works with fn too
  {:pre [(number? x) (number? y)]
   :post [(number? %) (pos? %)]}
  (+ (* x x) (* y y)))

Они полагаются на assert и, следовательно, могут быть отключены. (doc assert) для более подробной информации.

5 голосов
/ 07 октября 2011

Может быть, вы ищете записи?

(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[& 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

Проблемы типа «разрушение неправильной части списка» могут возникать из-за попытки закодировать что-то вроде «персонажа» в списке;тогда вам нужно запомнить такие вещи, как «почтовый индекс является четвертым элементом в списке« address »на третьей позиции в списке« person »».

В «классическом» Lisp вы можете решить это, написав метод доступафункции, в Clojure вы можете использовать записи.

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

Java IDE с автозаполнением может перехватить некоторые опечатки, пока вы еще печатаете, а статически типизированный язык перехватит многие из них во время компиляции, но в динамическом языке вы не найдете их до времени выполнения,Некоторые считают это недостатком динамических языков (включая Python, Ruby и т. Д.), Но, учитывая их популярность, многие программисты считают, что приобретенная гибкость и сохраненный код важнее, чем потеря автодополнения IDE и ошибки времени компиляции.

Принцип одинаков в обоих случаях: более ранние исключения лучше, так как для поиска причины требуется меньше кода.В идеале трассировка стека приведет вас прямо к опечатке.В Clojure записи и функции доступа дают вам это.

...