在Clojure中处理带前置条件的用户输入验证
我编写了一个游戏服务器,必须检查来自用户的消息是否正确有效。这意味着它们必须具有正确的语法,符合参数格式,并且语义正确,即符合游戏规则 我的目标是拥有一种表达性、功能性的方式,而不抛出异常,从而尽可能好地实现可组合性 我知道还有其他类似的问题,但它们要么是指我不喜欢的在Clojure中处理带前置条件的用户输入验证,clojure,Clojure,我编写了一个游戏服务器,必须检查来自用户的消息是否正确有效。这意味着它们必须具有正确的语法,符合参数格式,并且语义正确,即符合游戏规则 我的目标是拥有一种表达性、功能性的方式,而不抛出异常,从而尽可能好地实现可组合性 我知道还有其他类似的问题,但它们要么是指我不喜欢的{:pre…,:post…},因为一旦抛出异常,只能处理字符串化的信息,要么是指我不喜欢的异常处理,因为Clojure应该能够完成这类任务,或者他们引用哈斯凯尔的一元风格,例如一个可能的单子a(>[err such]),这也是我不喜
{:pre…,:post…}
,因为一旦抛出异常,只能处理字符串化的信息,要么是指我不喜欢的异常处理,因为Clojure应该能够完成这类任务,或者他们引用哈斯凯尔的一元风格,例如一个可能的单子a(>[err such])
,这也是我不喜欢的,因为Clojure应该能够在不需要单子的情况下处理这类任务
到目前为止,我使用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)))
类似的
(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 ...)))
(defn开始游戏[游戏id客户端]
(让"游戏")*
游戏(获取游戏id)
状态(:状态游戏)
玩家(:玩家游戏)]
(续)
(<(计数玩家)2):4120
(=状态:已启动):4093
(=状态:已完成):4100
:其他…)
另一种方法是编写一个宏,类似于
defn
和{:pre}
,但不是抛出一个AssertionError
抛出一个带有映射的ex-info
,而是:反对抛出异常。你问题的核心似乎是你的评论:
是的,也许我应该解释一下这是什么“丑陋”:这是仅仅返回一个实际结果的不可组合性,它几乎可以任意表示,并且一个编号的关键字作为错误。我的调用堆栈如下:
(>msg msg handler step handler step-N sub function)
,它们都可以返回这一个错误类型,但没有一个返回相同的成功类型,另一件事是,我必须手动设计en error返回类型是否短路,或者是否需要执行一些中间工作(撤消数据更改或同时通知其他客户端)
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)))
注意,在这个定义中,(pred->expr nil?form1 form2…
是(some->expr form1 form2…
),但现在我们可以使用其他谓词
例子
您的用例 一个灵活的选择是为验证错误制作一个包装器
(deftype ValidationError [msg])
然后,您可以将错误代码/消息包装为(>validationError4002)
,并将线程设置为
(pred-> msg #(instance? ValidationError %)
msg-handler step-handler step-N sub-function)
那么,这对你来说最理想的情况是什么?或者,为什么cond很难看?是的,也许我应该解释一下这是什么“难看”:只是返回一个实际结果的可组合性不好,这个结果几乎可以任意表示,一个编号的关键字作为错误。我的调用堆栈看起来像这样:
(->msg msg handler step handler step-N sub function)
,它们都可以返回这一个错误类型,但没有一个返回相同的成功类型,另一件事是,我必须手动设计en error返回类型是否短路,或者是否需要执行一些中间工作(撤消数据更改或同时通知其他客户端)。我想你说得一针见血。旁注:在我开始使用cond
之前,我使用了let?
宏,该宏功能非常强大,但看起来仍然像是代码中的黑客。我可能还需要重新考虑fdeftype
以使代码更具表现力。这是开源好处的一个很好的说明。虽然重写我实现了另一种模拟pred->
+deftype
行为的方法:一个人可以使用some->
,并在失败时让每一步返回(meta{:error 5000}nil)
。@kreisquadraur你有向后的meta
参数。你给出了映射{:error 5000} NIL元数据。交换它,你会得到一个异常。你不能将元数据分配给<代码> NIL。即使这样做,我也会认为它是滥用元数据。
(pred-> msg #(instance? ValidationError %)
msg-handler step-handler step-N sub-function)