Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by
Description of issue:

Suppose I want to create multiple bindings with let and then execute a body. I can do that easily like so:

(let [a 1
      b (inc a)
      c (* b b)]
  [a b c])


But, if I want to do the same type of thing with if-let, I can only do so by nesting them because if-let only accepts one binding at a time.

(if-let [a 1]
  (if-let [b (inc a)]
    (if-let [c (* b b)]
      [a b c]
      "error")
    "error")
  "error")


This is very inelegant because:
1) It is not as simple to read as it would be if all of the bindings were next to each other on the same indentation
2) The else clause it duplicated multiple times.
3) The else clause is evaluated in a different context depending which binding failed. What if a was already bound to something? If the if-let shadows a, and b does not get bound, the else clause would be executed with a different value bound to a than if a was not shadowed in the first if-let. (see code below for example)

I want to be able to write this instead:

(if-let [a 1
         b (inc a)
         c (* b b)]
  [a b c]
  "error")
=> [1 2 4]

(let [a :original]
  (if-let [a :shadowed
           b false]
          a a))
=> :original


I also want to be able to do a similar thing with when-let, if-some, and when-some.

*Proposed:*

I re-wrote those macros to be able to handle multiple bindings. If supplied with just one binding, their behavior remains identical. If supplied with multiple bindings, they should only execute the body if every binding passed. In the case of some bindings passing and some failing in if-let or if-some, none of the bindings should leak into the else clause.

*Patches:*

- clojure-core v2 8-3-2017.patch - Clojure patch with macro updates. For if-let and if-some, I had to add a bit of extra logic in order to prevent them from leaking bindings to the else clause in the case of some bindings passing and some failing. It also includes a few extra tests around each macro.
- core.specs.alpha.patch - core.specs.alpha patch with equivalent updates to core specs

5 Answers

0 votes
by

Comment made by: alexmiller

What's the relationship of this to CLJ-2007?

0 votes
by

Comment made by: justinspedding

I posted my solutions to that ticket as code in a comment. Then, you posted about the correct format of tickets and linked to the ticket creation guidelines. I figured that meant that you wanted a ticket to be made that followed the conventions.

Also, this ticket is about modifying the existing macros. CLJ-2007 was about creating 2 new macros: if-let* and when-let**.

0 votes
by

Comment made by: gshayban

It is worth looking at what the JVM is intending on doing with test-and-destructure intrinsics. Brian Goetz covers this in a recent talk on pattern matching (link: 1)

(link: 1) https://www.youtube.com/watch?v=n3_8YcYKScw

0 votes
by

Comment made by: justinspedding

An updated patch that simplifies the generated code when 0 bindings are given to if-let and if-some

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJ-2213 (reported by justinspedding)
...