On Jan 15, 5:02 pm, Simon Brooke <still...@googlemail.com> wrote:
> There's an old programmers hack that works in many languages of
> abusing the logical or operator to try a sequence of operations until
> one returns something useful. It appears that this works in Clojure,
> too.
>
> The reason I tried it is that I'm unhappy with Clojure's
> implementation of cond. Consider the following interaction:
>
> You are in a shed.
>     There is a box here
>     There is a knife here
> => open the box
> You open the box.
>     There is a knife in the box
> => Take the knife out of the box
> You take the knife out of the box
>
> Now, consider a recursive repertoire structure in which each element
> is a tuple {token, before-action, sub-repertoire, after-action} a
> subset of which is like this
>
>     (("take"
>          ()
>          (("the"
>             (bind-target-and-continue player (cdr line) (caddr
> repertoire) context)
>             (("out" () () (take-from-container player (cdr line)
> (caddr repertoire) context))
>             ))
>         (take-from-environment player (cdr line) (caddr repertoire)
> context)))
>
> The action parts may internally recurse into the interpreter; so for
> example bind-target-and-continue puts the token that follows "the"
> into the context as the target and then re-enters the interpreter with
> the remainder of the line and the sub-repertoire
>
> So given 'take the knife out of the box'
>
> ==> at "take" there is no before action, so we interpret the sub-
> repertoire
> ====> at "the" there is a before action, so we bind "knife" as target,
> and interpret the rest of the line with the sub-repertoire
> ======> at "out" there is no before-action or sub-repertoire, so we
> evaluate the after-action which tells us to treat what's left of the
> line ("of the box") as a container specifier, locate a container that
> matches that description in the environment, and take the knife out of
> it, rather than the knife in the environment
>
> But given just 'take the knife'
>
> ==> at "take" there is no before action, so we interpret the sub-
> repertoire
> ====> at "the" there is a before action, so we bind "knife" as target,
> and interpret the rest of the line with the sub-repertoire
> <==== but there is no rest-of-line, so interpreting it returns null
> ==> so we interpret the post action on take, and take the knife from
> the environment, not the one in the box.
>
> You can build up remarkably convincing interpreters for a subset of
> imperative English with this and a few Eliza style tricks. In portable
> standard lisp one would write
>
>     (cond
>       ((eval before-action))
>       ((interpret player sub-repertoire context))
>       ((eval after-action)))
>
> This is because each clause of the cond statement was an implicit do
> list, so
>
>     (let ((a 'foo)(b nil))
>       (cond
>         (a)
>         (b)))
>
> => foo
>
> Each of a and b are evaluated at most once (as a has a non-nil binding
> in this case b is not evaluated at all).
>
> To achieve this with Clojure's cond I have to do either
>
>     (let [a 'foo b nil]
>       (cond
>         a a
>         b b))
>
> => foo
>
> in which case a is evaluated twice; or
>
>     (let [a 'foo b nil]
>       (let [value-of-a a value-of-b b]
>         (cond
>           (not (= nil value-of-a))
>           value-of-a
>           (not (= nil value-of-b))
>           value-of-b)))
>
> => foo
>
> which only evaluates a once but it always evaluates b whether it needs
> to or not, /and/ it's fugly and hard to read, /and/ it's very likely
> inefficient (but someone more fluent in Clojure could probably replace
> my (not (= nil thing)) with something cleaner); or else
>
>     (let [a 'foo b nil]
>       (or a b))
>
> => foo
>
> which is simple and easy to read, but is exploiting the left-to-right
> evaluation strategy of the or operator. It isn't (or ought not to need
> to be) part of the contract of the or operator that it evaluates left
> to right. On a massively parallel machine, all branches of the or
> might be explored simultaneously. So this sort of hack leaves me
> feeling dirty.
>
> Comments?
>
> Is there (once again) something more elegant I've missed?

or has left-to-right and short-circuiting in its contract and will
never be executed in parallel (any parallel or would be a different
thing). The 'hack' is fine, and clean. As far as cond, as in many
areas, like 'let', Clojure opts out of the increased verbosity and
clutter of the additional grouping, admittedly losing the facility you
desire. It's a tradeoff I think serves the common case well.

Note that condp supports a ternary clause form that shovels the test
expr into the result expr (this more of a hack for your purposes):

(let [a 'foo b nil]
  (condp #(or %1 %2) nil
   a :>> identity
   b :>> identity))

=> foo

There has been some talk of adding :>> to cond also.

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

Reply via email to