Search code examples
clojuredestructuring

Destructuring map function arguments in clojure: does the map need to go last?


I'm trying to define a function that takes a map and a regular argument, and I'd like to destructure parts of the map, something like

(defn do-stuff
  [[{:keys [foo bar]} where] what]
  (println foo bar what))

but when I call the function I get an error

; Execution error (UnsupportedOperationException) at .../do-stuff (REPL:34).
; nth not supported on this type: PersistentArrayMap

If I swap function arguments

(defn do-stuff
  [what [{:keys [foo bar]} where]]
  (println foo bar what))

everything works fine. Of course I could just write a let inside the function and do the destructuring there, but I wonder what I'm missing...

EDIT: to clarify I expect to be able to call the function like

(do-stuff {:foo "something" :bar "something else"} "baz")

Solution

  • Since you fail to show us, what your call there is, my guess is, that you are confusing the let syntax for destructuring with the one on functions.

    So the following calls work - note the nesting in a vector of the map and the dangling where:

    (defn do-stuff
      [[{:keys [foo bar]} where] what]
      (println foo bar what))
    
    (do-stuff [{:foo 1 :bar 2} 3] 4)
    ; 1 2 4
    
    (defn do-stuff
      [what [{:keys [foo bar]} where]]
      (println foo bar what))
    
    (do-stuff 0 [{:foo 1 :bar 2} 3])
    ; 1 2 0
    

    Although you don't print where, it seems like you want to hold on to the map itself. But this is done via :as.

    (defn do-stuff
      [{:keys [foo bar] :as where} what]
      (println foo bar where what))
    
    (do-stuff {:foo 1 :bar 2 :baz 3} 4)
    ; 1 2 {:foo 1, :bar 2, :baz 3} 4
    

    edit

    The reason, why things happening as they are happening falls back to this:

    Positional destructuring works for strings (it gives the character on the position, but then the map-destructuring fails, because a char is no map. But it fails gracefully and just assigns nil to the two key variables.

    But when a map is provided instead it fails hard, because they can not destructured positionally (accessed via nth), hence the error message.

    (defn do-stuff
      [[{:keys [foo bar]}]]
      (println foo bar))
    
    (do-stuff "baz")
    ; nil nil
    (do-stuff {:foo "something" :bar "something else"})
    ; throws