First off, I should say that you should first consider alternative 
approaches before considering the options below.  For example, cond seems 
well-suited for this particular case:


(defn verify-position[pos type]
 (cond (nil? pos)     true
       (or (> pos 90)
           (< pos 0)) false

       .
       .
       .

       :else true))


However, sometimes you really do want imperative-style control flow.  Two 
possibilities come to mind here:

   1. Monads (e.g., algo.monads <https://github.com/clojure/algo.monads>)
   2. Continuations (e.g., pulley.cps 
   <https://github.com/positronic-solutions/pulley.cps>)

In fact, monads and (delimited) continuations are isomorphic.  The main 
difference is one of view-point -- the essence of monads is the composition 
of computations; the essence of continuations is control flow.

For a monadic approach, I would start with something like the "Maybe" 
monad, but add a monadic 'bail' operator (to use your terminology and avoid 
conflicting with the common use of 'return' as one of the fundamental 
operators on monads).  Essentially, 'bail' would be constructed to abort 
the "current computation".  Then if you implement a function in terms of 
the 'bailing' monad (and encapsulate the effect to that function), you can 
use 'bail' to effectively return a value from the function at any point in 
the computation.

With continuations, all you need is an "abortive" continuation back to the 
call-site of the function.  With pulley.cps, this is easily accomplished 
using call-cc / let-cc.  For example, here's your first example in terms of 
let-cc:


;; Note:  This must be wrapped in an`cps` form,
;;        or use `def` along with `cps-fn`
(defn verify-position[pos type]
 (let-cc [return]
   (when(nil? pos)
     (return true))

    (when (or (> pos 90)
              (< pos 0))
      (return false))

    .
    .
    .

    true))


Basically, we just wrap the function's body in let-cc.  This gives us 
access to the current continuation of function (i.e., the place where the 
function was called).  In this case, we bind that continuation to the name 
return.  By invoking return, we abort the current continuation at that 
point and restore the captured continuation.  I've used when instead of if 
to check for conditions that return, because it more clearly conveys that 
effects are used.  But in any case, it's a pretty straight-forward 
conversion from the python code.

Using continuations you can build more complex control flows as well.  For 
instances, you can "return" not just from a function but from any point in 
the computation -- all you need to do is have access to the continuation at 
that point.  And since pulley.cps makes continuations first-class objects, 
you can bind them to variables (including dynamic vars), pass/return them 
to/from functions, etc.

Hopefully this helps.  Like I said, explore other options first, but 
continuations and/or monads are there if you decide to use them.

Nathan Davis

On Thursday, May 26, 2016 at 9:50:24 AM UTC-5, 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