在Clojure中使用前提条件处理用户输入验证

Kre*_*tur 5 clojure

我写了一个游戏服务器,并且必须检查来自用户的消息是否正确和有效.这意味着它们必须具有正确的语法,符合参数格式并且在语义上是正确的,即符合游戏规则.

我的目标是以一种富有表现力的功能方式,而不会抛出允许尽可能好的可组合性的异常.

我知道其他类似的问题,但是他们要么提到{:pre ..., :post ...}我不喜欢的,因为一旦抛出异常只能处理字符串化的信息,或者引用一般我不喜欢的异常处理,因为Clojure应该能够完成这种任务,或者他们提到了Haskell的monadic风格,例如(-> [err succ])我也不喜欢Monadàla,因为Clojure应该能够在不需要Monad的情况下处理这类任务.

到目前为止,我用丑陋的方式使用cond作为前置条件检查器和错误代码,然后我将其发送回发送请求的客户端:

(defn msg-handler [client {:keys [version step game args] :as msg}]
  (cond
    (nil? msg)                     :4001
    (not (valid-message? msg))     :4002
    (not (valid-version? version)) :5050
    (not (valid-step?    step))    :4003
    (not (valid-game-id? game))    :4004
    (not (valid-args?    args))    :4007
    :else (step-handler client step game args)))
Run Code Online (Sandbox Code Playgroud)

和类似的......

(defn start-game [game-id client]
  (let [games   @*games*
        game    (get games game-id)
        state   (:state game)
        players (:players game)]
    (cond
      (< (count players) 2) :4120
      (= state :started)    :4093
      (= state :finished)   :4100
      :else ...)))
Run Code Online (Sandbox Code Playgroud)

另一种方式是写一个宏,类似于defn{:pre}但不是抛出AssertionError抛出一个ex-info与地图,但再次:反对异常抛出.

A. *_*ebb 3

您问题的核心似乎在您的评论中:

是的,也许我应该解释一下这是“丑陋的”:这是仅返回实际结果的可组合性不佳,它几乎可以任意表示,并且编号关键字作为错误。我的调用堆栈看起来像这样:(-> msg msg-handler step-handler step-N sub-function),所有这些都可以返回这个错误类型,但是它们都没有返回相同的成功类型,另一件事是,我必须手动设计错误返回类型是否应该短路或必须预计要做一些中间工作(撤消数据更改或并行通知其他客户端)

Clojure 1.5+ 具有some->线程宏来摆脱nil?检查样板。我们只需要轻轻调整代码以用nil?我们选择的检查代替检查。

(defmacro pred->
  "When predicate is not satisfied, threads expression into the first form
  (via ->), and when that result does not satisfy the predicate, through the
  next, etc. If an expression satisfies the predicate, that expression is
  returned without evaluating additional forms."
  [expr pred & forms]
  (let [g (gensym)
        pstep (fn [step] `(if (~pred ~g) ~g (-> ~g ~step)))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (map pstep forms))]
       ~g)))
Run Code Online (Sandbox Code Playgroud)

注意这个定义,(pred-> expr nil? form1 form2 ...)(some-> expr form1 form2...)。但现在我们可以使用其他谓词。


例子

(defn foo [x] (if (even? x) :error-even (inc x)))
(defn bar [x] (if (zero? (mod x 3)) :error-multiple-of-three (inc x)))

(pred-> 1 keyword? foo) ;=> 2
(pred-> 1 keyword? foo foo) ;=> :error-even
(pred-> 1 keyword? foo foo foo) ;=> :error-even

(pred-> 1 keyword? foo bar foo bar) ;=> 5
(pred-> 1 keyword? foo bar foo bar foo bar foo bar) ;=> :error-multiple-of-three
Run Code Online (Sandbox Code Playgroud)

您的用例

一个灵活的选择是为验证错误制作一个包装器

(deftype ValidationError [msg])
Run Code Online (Sandbox Code Playgroud)

然后您可以将错误代码/消息包装(->ValidationError 4002)

(pred-> msg #(instance? ValidationError %) 
  msg-handler step-handler step-N sub-function)
Run Code Online (Sandbox Code Playgroud)