Search code examples
clojurebackend

How do I interpret merge-with that takes a fn in clojure?


I'm currently learning Clojure and I'm a total beginner and I would appreciate some help understanding. I went through some code today and found this.

(let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )])

where mf, swt, timepoint-max and timepoint-sum looks something like

{"Timepoint1": 3, "Timepoint2": 2}

So what does the code above do?

I understand that we set the variable timepoints to be some sort of union between the two maps(?). But I'm especially confused about the fn [mf swt] [mf swt] part.


Solution

  • The expression (fn [mf swt] [mf swt]) is an anonymous clojure function that constructs a vector from the two arguments passed into it, e.g

    ((fn [mf swt] [mf swt]) :a :b)
    ;; => [:a :b]
    

    The expression (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum ) is a call to the function merge-with with first argument (fn [mf swt] [mf swt]), second argument timepoint-max and third argument timepoint-sum. Here is an example where we bind timepoint-max and timepoint-sum to some example values:

    (def timepoint-max {:x 0 :y 1})
    (def timepoint-sum {:y 100 :z 200})
    
    (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum)
    ;; => {:x 0, :y [1 100], :z 200}
    

    Read the docs of merge-with to understand what it does:

    Returns a map that consists of the rest of the maps conj-ed onto the first. If a key occurs in more than one map, the mapping(s) from the latter (left-to-right) will be combined with the mapping in the result by calling (f val-in-result val-in-latter).

    In the above example, what we actually compute is the same as

    {:x (:x timepoint-max)
     :y ((fn [mf swt] [mf swt]) (:y timepoint-max) (:y timepoint-sum))
     :z (:z timepoint-sum)}
    ;; => {:x 0, :y [1 100], :z 200}
    

    where the function (fn [mf swt] [mf swt]) is used to combine the two values at the only overlapping key :y.

    The full expression (let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )]) is a let form that binds values to symbols but it is not very useful because its *exprs part is empty, so it always evaluates to nil:

    (let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )])
    ;; => nil
    

    In order for it to evaluate to something else, e.g. timepoints, it would have to be modified to something like

    (let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum) ] 
      timepoints)
    ;; => {:x 0, :y [1 100], :z 200}