如何在clojure中构建健壮的数据apis

jay*_*100 9 types protocols clojure decoupling code-completion

嗨伙计们:我发现我的clojure应用程序由于缺少数据API而在结构上非常快速地耦合... - 我有带有名称的键映射,如果输入错误,会导致抛出异常或错误.我还注意到,在对列表进行解构时很容易出错(例如,你可能会破坏列表中错误的部分).....

来自java世界,通常我使用我的IDE帮助我从最小的,无序的数据对象中获取"正确的"数据---但是clojure map传递似乎是与此相反的范例.

在没有类型系统或ide代码完成的情况下,clojurians如何进行防御性编码?

j-g*_*tus 5

也许你在找记录?

(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"}
Run Code Online (Sandbox Code Playgroud)

现在,您可以通过使用函数(例外)或关键字(非例外)访问数据来选择是否要为错误输入的名称设置例外.

=> (.fax p) 
java.lang.IllegalArgumentException: 
    No matching field found: fax for class user.Person
=> (:fax p)
nil
Run Code Online (Sandbox Code Playgroud)

此方法要求您避免使用与现有方法冲突的字段名称.(见@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
Run Code Online (Sandbox Code Playgroud)

"解构列表的错误部分"类型问题可能来自于尝试在列表中编码类似"人"的内容; 那么你需要记住诸如"邮政编码是'人'列表中第三位'地址'列表中的第四个元素"之类的东西.

在'经典'Lisp中你可以通过编写访问器函数来解决这个问题,在Clojure中你可以使用记录.

错别字将导致任何编程语言出现问题,您可以做的最好的事情就是尽早抓住它们.

具有自动完成功能的Java IDE可能会在您仍在键入时捕获一些拼写错误,并且静态类型语言将在编译时捕获其中许多错误,但在动态语言中,您将无法在运行时找到它们.有些人认为这是动态语言(包括Python,Ruby等)的缺点,但考虑到它们的普及,相当多的程序员认为获得的灵活性和代码保存比丢失IDE自动完成和编译时错误更重要.

在任何一种情况下,原则都是相同的:早期的例外情况更好,因为需要更少的代码来查找原因.理想情况下,堆栈跟踪会引导您直接输入错字.在Clojure中,记录和访问器功能为您提供了这些功能.

  • 但是没有名为“size”的记录字段。对 `(.size rec)` 的调用将调用在 `java.util.Collection` 中定义的 *method* `size`,对于 Clojure 记录具有的所有接口中的所有 nullary 方法也是如此。 (2认同)

cgr*_*and 5

为你的"模式"(密钥以及值类型等)编写验证器函数,然后在代码中的前后条件中使用它们 - 因为它们的语法鲜为人知,这是一个快速的复习:

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

他们依赖assert,因此可以被禁用.(doc assert)更多细节.