[ANN] com.walmartlabs/cond-let 1.0.0

227 views
Skip to first unread message

Howard Lewis Ship

unread,
Oct 3, 2018, 12:44:07 PM10/3/18
to clo...@googlegroups.com
A micro library of a single macro, cond-let.

cond-let acts like a cond, but adds :let terms that are followed by a binding form (like let).

This allows conditional code to introduce new local symbols; the result is clearer, more linear code, that doesn't make a march for the right margin.

Example:

(defn ^:private has-necessary-capabilities?
  "Does the worker have all the capabilities that the job needs?"
  [state worker-id task]
  (cond-let

    :let [job-id (:job-id task)]

    (nil? job-id)
    true

    :let [capabilities (get-in state [:jobs job-id :clockwork/required-capabilities])]

    (empty? capabilities)
    true

    :let [worker-capabilities (get-in state [:workers worker-id :capabilities])]

    (empty? worker-capabilities)
    false

    :else
    ;; It's ok for the worker to have *more* capabilities than are specified.
    ;; For each required capability, we need an exact match.
    (= (select-keys worker-capabilities (keys capabilities))
       capabilities)))


--
Howard M. Lewis Ship

Senior Mobile Developer at Walmart Labs

Matching Socks

unread,
Oct 3, 2018, 8:05:20 PM10/3/18
to Clojure
Is this a refinement of Mark Engelberg's "better-cond", or an alternative approach? 

I have not used better-cond myself, but it starts here:  https://dev.clojure.org/jira/browse/CLJ-200.

Mark Engelberg

unread,
Oct 3, 2018, 10:22:12 PM10/3/18
to clojure
This looks like a case of "convergent evolution".

Having the ability to do a :let in the middle of a cond feels like one of those things that should be in the core language, so if it's not in there, a bunch of people are naturally going to arrive at the same solution and make it happen in their own utility libraries.  A bunch of us Clojure programmers from the early 1.0 days had been privately passing around and using a "cond that supports :let bindings" macro for years.  The first time I saw the macro was in a blog post by Christophe Grand. I really hoped it would make it into Clojure proper -- other functional languages like Racket and F# support ways to bind local variables with "clearer, more linear code, that doesn't make a march for the right margin", as Howard Lewis Ship put it.  But after several years had passed without any indication that CLJ-200 was ever going to be addressed, I eventually made the improved cond macro into a clojars library.

walmartlabs' cond-let addresses the most important thing (let), which is the critical piece of functionality that feels like the most natural, needed addition to the language.  better-cond's :let syntax is identical.  But as us old-school Clojurians passed around the "better cond" macro over the years, it grew in functionality.  So in better-cond, I included the other little improvements that had accumulated over time, which I had found useful.  So better-cond also supports :when, :when-let, and :do (and will soon have :when-some).  :let is the only piece that I felt really belonged in the core language's cond, and if CLJ-200 had made it into the core language, I would have been content to just use Clojure's own cond.  But once I realized I was going to need a library to achieve the much-needed :let inside of cond, I figured I might as well use that library to include the other convenient cond additions as well.  So better-cond is a superset of cond-let's functionality, with support for :let plus a few bonuses.

Use whichever one strikes your fancy.  cond-let is perfect if all you care about is adding :let to your cond.  If you want to experiment with some of the other features beyond :let, you could use better-cond and see what you think.

Either way, I strongly encourage you to use one of these two libraries so you can start using :let inside your cond.  I agree fully with Howard Lewis Ship that it results in clearer code.  Try either library which supports this -- it will change your life!


On Wed, Oct 3, 2018 at 5:05 PM Matching Socks <phill...@gmail.com> wrote:
Is this a refinement of Mark Engelberg's "better-cond", or an alternative approach? 

I have not used better-cond myself, but it starts here:  https://dev.clojure.org/jira/browse/CLJ-200.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Gary Trakhman

unread,
Oct 4, 2018, 11:10:44 AM10/4/18
to clo...@googlegroups.com
These are all just sugar over monadic bind, right?

Here's one way to do it in the ocaml alternate universe:

But it can be made to work for async or options or whatever, too.


The general idea is turning function application into something that looks less nested.

Moe Aboulkheir

unread,
Oct 4, 2018, 11:38:05 AM10/4/18
to clo...@googlegroups.com
See https://funcool.github.io/cats/latest/#mlet for something closer to home, in the monadic vein.

lei Shulang

unread,
Oct 4, 2018, 12:57:55 PM10/4/18
to Clojure
But a Maybe/Nothing will short-circuit the whole flow where cond-let won't? 

Gary Trakhman

unread,
Oct 4, 2018, 1:06:43 PM10/4/18
to clo...@googlegroups.com
The short-circuiting is a 'feature' of letting the type control the sequencing of operations.  In practice you mix multiple interacting monads depending on what your requirements are, for example I regularly work with Deferred Options and Deferred Results.  In clojure, you could try to add a 'if-let' style nil-punning thing to a cond-let implementation but at that point I imagine it more appropriate to take a step back instead :-). 

I'm not convinced yet that it's worth thinking about it like this in a dynamically typed language, but brought it up because there's a named concept that addresses the need people feel to extend cond/lets.

Howard Lewis Ship

unread,
Oct 4, 2018, 2:33:59 PM10/4/18
to clo...@googlegroups.com
Yes, I wouldn't have bothered if I had known about better-cond, so there you go.  I think I first wrote this code at Aviso at least five years ago.

Alan Thompson

unread,
Oct 4, 2018, 4:36:43 PM10/4/18
to clojure
How would the :when and :do forms work?
Alan

On Wed, Oct 3, 2018 at 7:22 PM Mark Engelberg <mark.en...@gmail.com> wrote:

Mark Engelberg

unread,
Oct 5, 2018, 1:39:43 AM10/5/18
to clojure
Documentation for latest features in the 2.0.1 branch: https://github.com/Engelberg/better-cond/tree/v2.0.1

An example:
 (cond
   (odd? a) 1
   :let [a (quot a 2)]
   :when-let [x (fn-which-may-return-nil a),
              y (fn-which-may-return-nil (* 2 a))]
   :when (seq x)
   :do (println x)
   (odd? (+ x y)) 2
   :else 3)
The :do performs a side-effecting statement if it gets that far in the cond.  :do is my favorite bonus addition for cond beyond :let.  I find I use it a lot to quickly insert some debugging print commands without needing to change the shape of my code, so it can be trivially removed later.

When :do did not exist, but :let did exist in my cond macro, I found myself frequently writing things like:
:let [_ (println x)]
in order to insert a side-effecting statement into the cond sequence.
:do is a cleaner solution.

:when only continues with the cond if the value is truthy, otherwise it bails out of the cond with nil.
:when-some (not yet merged in) will do much the same thing, but continuing if non-nil, rather than truthy.

:when, :when-some, and :when-let don't add a whole lot of richness.  For example, that :when line in the example could just have easily been written as:
(not (seq x)) nil

But using these can potentially add clarity of intention, so even though I could make do without them, I go ahead and use them when relevant.

In better-cond's cond macro, that final :else is optional. You may prefer the new cond-let library if you want to continue enforcing the use of :else on the last clause.

To reiterate, :let is by far the most valuable addition, and I find :do to be the second-most valuable addition.  Other additions are fairly minor syntactic sugar.

Howard Lewis Ship

unread,
Oct 5, 2018, 12:13:53 PM10/5/18
to clo...@googlegroups.com
I'm glad my little library has gotten some attention on better-cond, which even I'm switching over to.
Reply all
Reply to author
Forward
0 new messages