I use a little short circuited threading macro to deal with this. Here's a gist with the macro named short-> and how I would write out your first 2 validation steps.
https://gist.github.com/mikeball/10cc64fe97c119671918fb2d1d8b4118 The new spec stuff looks really interesting, haven't had a chance to look into it yet. On Thursday, May 26, 2016 at 7:50:24 AM UTC-7, John Szakmeister wrote: > > I'm very much a fan of bailing out early in imperative > programming as it helps to avoid a bunch of nested if conditions > that are to follow and read. This typically comes up when > checking arguments to ensure that they're valid (in range, of > acceptable values, etc). One of the major stumbling blocks > I've had when writing Clojure is to find a good way to keep code > concise, but readable. > > For instance, take a snippet like this: > > def verify_position(pos, type): > # It's acceptable to have a None value--it just means don't > # change the position for the axis. > if pos is None: > return True > > # Anything outside our limits is invalid. > if (pos > 90) or (pos < 0): > return False > > if type == 'switched' and pos not in (0, 90): > # Switched axes only allow 0 and 90, and nothing in > # between. > return False > > if type == 'dual': > # We can't control the value on this axis, so anything > # other than None is invalid. > return False > > return True > > > I find this very readable in that along the way, I can start > throwing things off the raft: after the first condition, I don't > need to worry about None being present. After the second, I > know the value is within limits, etc. I have a hard time > translating the above into (what I believe) is readable Clojure. > Here's my stab at it: > > (defn verify-pos [pos axis-type] > (if (nil? pos) > ;; nil means don't move the axis. > true > (case axis-type > ;; Only 0 and 90 are allowed on a switched axis. > "switched" (or (= pos 0) > (= pos 90)) > > ;; Any non-nil value is disallowed on dual. > "dual" false > > ;; Otherwise, make sure we're within a valid range. > (and (<= pos 90) > (>= pos 0))))) > > Now, this was a rather simple example, but you can see some of > the nesting starting. Add in another condition like > nil?--something that is somewhat global across the different > types--and you get another level of nesting in there. > > I can break it up more: > > (defn verify-range [pos axis-type] > (case axis-type > ;; Only 0 and 90 are allowed on a switched axis. > "switched" (or (= pos 0) > (= pos 90)) > ;; Any non-nil value is disallowed on dual. > "dual" false > ;; Otherwise, make sure we're within a valid range. > (and (<= pos 90) > (>= pos 0)))) > > (defn verify-pos [pos axis-type] > (or (nil? pos) > (verify-range pos axis-type))) > > And this is a bit better, but you run up against another issue: > coming up with good names for each part of the whole so that you > can combine them. And, coming up with names that don't sound so > similar that folks have to dig into the implementation to know > which one is the correct one (I feel the above break up has this > problem). > > In some cases, the error checking logic is really > complicated--because the thing being controlled has complex > restrictions that are out of my control--and the nesting of if > conditionals is maddening. Having to come up with names for > each of them would be more than twice as frustrating, as the > individual bits don't lend themselves to good names. > > Then there's the issue of combining the verification and the > actual work into a useful function, where you need to verify and > then act on it: > > (defn set-position [pos type] > (if (verify-pos pos type) > (send-position-to-equip pos) > (do-something-for-invalid-input))) > > Again, this is a simple example, but more complicated cases have > more to check, and therefore more nesting of if statements, > where the early bail technique leaves the flow pretty readable. > I also realize pre- and post- conditions might be useful, but I > don't feel they're appropriate when validating user > input--especially not assertion errors, which we generally think > of programming errors, not user input errors. > > I realize some of this may be me and shaping my mind more to > functional programming, though I do feel like I have a decent > grasp of it. I've been dabbling in Clojure for several years, > and have more recently incorporated ClojureScript into a project > for our web front-end. However, I feel like I might be missing > an important strategy for this kind of problem, and I'm hoping > that one of you can point me in a good direction. And please > don't get hung up on this example. I was trying to boil > something much more difficult down into something reasonable > that would help show the issue. > > Thank you! > > -John > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.