Why don't when-let and if-let support multiple bindings by default?

前端 未结 3 422
悲&欢浪女
悲&欢浪女 2020-12-29 23:54

Why don\'t when-let and if-let support multiple bindings by default?

So:

(when-let [a ...
           b ...]
  (+ a b))


        
相关标签:
3条回答
  • 2020-12-30 00:15

    If you use cats, then there is a mlet function that you might find useful :

    (use 'cats.builtin)
    (require '[cats.core :as m])
    (require '[cats.monad.maybe :as maybe])
    
    (m/mlet [x (maybe/just 42)
             y nil]
      (m/return (+ x y)))
    ;; => nil
    

    As you can see, the mlet short-circuits when encountering a nil value.

    (from section 6.5.1 nil)

    0 讨论(0)
  • 2020-12-30 00:21

    Because (for if-let, at least) it's not obvious what to do with the "else" cases.

    At least, motivated by Better way to nest if-let in clojure I started to write a macro that did this. Given

    (if-let* [a ...
              b ...]
      action
      other)
    

    it would generate

    (if-let [a ...]
      (if-let [b ...]
        action
        ?))
    

    and it wasn't clear to me how to continue (there are two places for "else").

    You can say that there should be a single alternative for any failure, or none for when-let, but if any of the tests mutate state then things are still going to get messy.

    In short, it's a little more complicated than I expected, and so I guess the current approach avoids having to make a call on what the solution should be.

    Another way of saying the same thing: you're assuming if-let should nest like let. A better model might be cond, which isn't a "nested if" but more an "alternative if", and so doesn't fit well with scopes... or, yet another way of saying it: if doesn't handle this case any better.

    0 讨论(0)
  • 2020-12-30 00:30

    Here is when-let*:

    (defmacro when-let*
      "Multiple binding version of when-let"
      [bindings & body]
      (if (seq bindings)
        `(when-let [~(first bindings) ~(second bindings)]
           (when-let* ~(vec (drop 2 bindings)) ~@body))
        `(do ~@body)))
    

    Usage:

    user=> (when-let* [a 1 b 2 c 3]
                    (println "yeah!")
                    a)
    ;;=>yeah!
    ;;=>1
    
    
    user=> (when-let* [a 1 b nil c 3]
                    (println "damn! b is nil")
                    a)
    ;;=>nil
    


    Here is if-let*:

    (defmacro if-let*
      "Multiple binding version of if-let"
      ([bindings then]
       `(if-let* ~bindings ~then nil))
      ([bindings then else]
       (if (seq bindings)
         `(if-let [~(first bindings) ~(second bindings)]
            (if-let* ~(vec (drop 2 bindings)) ~then ~else)
            ~else)
         then)))
    

    Usage:

    user=> (if-let* [a 1 
                     b 2 
                     c (+ a b)]
                  c
                  :some-val)
    ;;=> 3
    
    user=> (if-let* [a 1 b "Damn!" c nil]
                  a
                  :some-val)
    ;;=> :some-val
    

    EDIT: It turned out bindings should not be leaked in the else form.

    0 讨论(0)
提交回复
热议问题