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