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.

Reply via email to