5

I write a game server and have to check that messages arriving from users are correct and valid. That means they have to be of correct syntax, comply to parameter formatting and are semantically correct, i.e. complying to the game's rules.

My goal is to have an expressive, functional way without throwing exceptions that allows composability as good as possible.

I am aware of other similar questions but they either refer to {:pre ..., :post ...} which I dislike as only stringified information can be processed once the exception is thrown, or refer exception handling in general which I dislike because Clojure should be able to do this kind of task, or they refer to Haskell's monadic style with e.g. a maybe Monad à la (-> [err succ]) which I also dislike because Clojure should be able to handle this kind of task without needing a Monad.

So far I do the ugly way using cond as pre-condition checker and error codes which I then send back to the client who send the request:

(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)))

and similar...

(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 ...)))

Another way would be to write a macro, similar to defn and {:pre} but instead of throwing an AssertionError throw an ex-info with a map, but again: opposed to exception throwing.

2
  • So, what would this look like ideally for you? Or, why is cond ugly? Commented Apr 27, 2014 at 15:04
  • Yeah, maybe I should explain what about this is "ugly": It's the not-well composability of just return an actual result, which can be represented almost arbitrarily and a numbered keyword as error. My callstack looks like this: (-> msg msg-handler step-handler step-N sub-function), and all of them could return this one error type, but none of them returns the same success type and another thing is, that I have to manually design if en error return type should short-circuit or has to be expected to do some intermediate work (undo data changes or notify other clients in parallel). Commented Apr 27, 2014 at 15:57

1 Answer 1

3

The heart of your question seems to be in your comment:

Yeah, maybe I should explain what about this is "ugly": It's the not-well composability of just return an actual result, which can be represented almost arbitrarily and a numbered keyword as error. My callstack looks like this: (-> msg msg-handler step-handler step-N sub-function), and all of them could return this one error type, but none of them returns the same success type and another thing is, that I have to manually design if en error return type should short-circuit or has to be expected to do some intermediate work (undo data changes or notify other clients in parallel)

Clojure 1.5+ has the some-> threading macro to get rid of nil? check boilerplate. We just need to gently adjust the code to substitute the nil? check for a check of our choice.

(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)))

Note with this definition, (pred-> expr nil? form1 form2 ...) is (some-> expr form1 form2...). But now we can use other predicates.


Example

(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

Your use case

A flexible choice would be to make a wrapper for validation errors

(deftype ValidationError [msg])

Then you can wrap your error code/messages as in (->ValidationError 4002) and change your threading to

(pred-> msg #(instance? ValidationError %) 
  msg-handler step-handler step-N sub-function)
Sign up to request clarification or add additional context in comments.

4 Comments

I think you hit the nail on the head. Side note: before I started to use cond I used the let? macro from github.com/egamble/let-else which is pretty powerful, but still just looks like a hack in the code. I might also have to reconsider fdeftype to be more expressive in the code.
A nice illustration of the benefits of open source.
While rewriting I realised another way to emulate the pred-> + deftype behaviour: One could use some-> and let each step return (with-meta {:error 5000} nil) on failure.
@Kreisquadratur You have the arguments to with-meta backwards there. You are giving the map {:error 5000} nil meta-data. Swap it around and you'll get an exception. You can't assign meta-data to nil. Even if this did work, I'd consider it an abuse of meta-data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.