I think the idiomatic way to handle this in clojure is to do all your
validation upfront on a single descriptive data structure, perhaps in a
single function, then bail early.  That has the added advantage of being
able to report multiple errors, instead of just the first, and is well
supported by libs like Schema and the new clojure.spec.

Maybe if the target of the code is by nature imperative, it would make this
more difficult.  You could probably abuse exceptions to avoid the nesting.

On Thu, May 26, 2016 at 10:50 AM John Szakmeister <j...@szakmeister.net>
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.
>

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