Functionality of update vs update-in for non-nested structures - clojure

I was looking over some Quil examples, and noticed that 2 different authors (for the "Hyper" and "Equilibrium" examples) used:
(update-in s [:x] + dx vx)
instead of simply
(update s :x + dx vx)
Is there a reason for this? If s was a deeply nested structure, ya, it would make sense. In both cases though, the list of keys only has 1 entry, so to me, the 2 snippets above seem equivalent:
(let [dx 1
vx 2
s {:x 5}]
(println (update-in s [:x] + dx vx))
(println (update s :x + dx vx)))
{:x 8}
{:x 8}
Except that update-in will probably have a little bit more overhead.
The only reason I could think of is if they make the state nested in the future, it will ease the transition. For such a simple example though, this seemed unlikely, especially given there are magic constants everywhere.
Is there any reason to use update-in over update when the structure isn't nested?

There is no reason to use update-in for a non-nested structure and update is preferred.

If you look at the source code, you will see they both use assoc under the covers. There is no reason to prefer one over the other except for style and code clarity, taking nearby and related code into account.
Also, update was not added until Clojure 1.7, which may play a role in the choice.
P.S. If you are ever looking for the missing function dissoc-in you can find it in the Tupelo library.

Related

What are the names in other lisps for Clojure's assoc-in?

Context
Our team is using some more functional patterns in other languages (IE javascript and ruby) and we've had some recreational conversations on standardized naming. My familiarity is largely limited to Clojure and Ramda.js.
Both Clojure and Ramda have assoc and Clojure has assoc-in with a Ramda version at assocPath. I'm guessing Ramda cribbed its name from Clojure here.
I note that common lisp doesn't seem to even have a clear comparable to assoc-in. See this conversation.
The Question
What other lisps or lisp-likes have assoc and assoc-in comparables, and what do they call them?
Bonus Points
Do there exist any resources for "rosetta stone" inter-lisp comparison of function names? A place to easily see where comparable behavior has similar or different names, or to see where comparable names have similar or different behavior?
You're right that Common Lisp doesn't have assoc-in or something similar. I would say, overall, it has pretty basic support for hash-table operations in the standard library (which I attribute to lack of practical experience with them at the time of the development of the standard). However, even if more hash-table-related stuff was present, I still doubt something like assoc-in would be added. That's because, in Lisp, the preferred approach to deal with assignment is via the setf'able places machinery that involves the macro define-setf-expander.
For instance, the rutils library I am maintaining provides a lot of additional hash-table-related operators, as well as Clojure-like literal syntax for them. Yet, this is how I'd handle the assoc-in case using its primitives (including the ? operator that is a shorthand for generic-elt):
CL-USER> (ql:quickload :rutils)
CL-USER> (in-package :rtl-user)
RTL-USER> (named-readtables:in-readtable rutils-readtable)
RTL-USER> (toggle-print-hash-table)
RTL-USER> (let ((ht #{:a 1 :b 3}))
(setf (? ht :c) #{:d 33})
ht)
#{
:A 1
:B 3
:C #{
:D 33
}
}
I'd say that it's a question of taste which approach to prefer. To me, the Lisp one has more advantages: it's more uniform and explicit.
The other advantage is that the get-in part becomes really straightforward:
RTL-USER> (? * :c :d)
33
(Here, I use * to refer to the results of the previous REPL computation that happen to be the hash-table we created in the assoc-in exercise).
P.S. As for the Rosetta stones, here are a few attempts:
https://hyperpolyglot.org/lisp
https://lispunion.org/rosetta/

How can you destructure in the REPL?

Suppose I've got a function (remove-bad-nodes g) that returns a sequence like this:
[updated-g bad-nodes]
where updated-g is a graph with its bad nodes removed, and bad-nodes is a collection containing the removed nodes.
As an argument to a function or inside a let, I could destructure it like this:
(let [[g bads] (remove-bad-nodes g)]
...)
but that only defines local variables. How could I do that in the REPL, so that in future commands I can refer to the updated graph as g and the removed nodes as bads? The first thing that comes to mind is this:
(def [g bads] (remove-bad-nodes g)
but that doesn't work, because def needs its first argument to be a Symbol.
Note that I'm not asking why def doesn't have syntax like let; there's already a question about that. I'm wondering what is a convenient, practical way to work in the REPL with functions that return "multiple values". If there's some reason why in normal Clojure practice there's no need to destructure in the REPL, because you do something else instead, explaining that might make a useful answer. I've been running into this a lot lately, which is why I'm asking. Usually, but not always, these functions return an updated version of something along with some other information. In side-effecting code, the function would modify the object and return only one value (the removed nodes, in the example), but obviously that's not the Clojurely way to do it.
I think the way to work with such functions in the repl is just to not def your intermediate results unless they are particularly interesting; for interesting-enough intermediate results it's not a big hassle to either def them to a single name, or to write multiple defs inside a destructuring form.
For example, instead of
(def [x y] (foo))
(def [a b] (bar x y))
you could write
(let [[x y] (foo),
[x' y'] (bar x y)])
(def a x') ; or maybe leave this out if `a` isn't that interesting
(def b y'))
A nice side effect of this is that the code you write while playing around in the repl will look much more similar to the code you will one day add to your source file, where you will surely not be defing things over and over, but rather destructuring them, passing them to functions, and so on. It will be easier to adapt the information you learned at the repl into a real program.
There's nothing unique about destructuring w/r/t the REPL. The answer to your question is essentially the same as this question. I think your options are:
let:
(let [[light burnt just-right] (classify-toasts (make-lots-of-toast))]
(prn light burnt just-right))
def the individual values:
(def result (classify-toasts (make-lots-of-toast)))
(def light (nth result 0))
(def burnt (nth result 1))
(def just-right (nth result 2))
Or write a macro to do that def work for you.
You could also consider a different representation if your function is always returning a 3-tuple/vector e.g. you could alternatively return a map from classify-toasts:
{:light 1, :burnt 2, :just-right 3}
And then when you need one of those values, destructure the map using the keywords wherever you need:
(:light the-map) => 1
Observe:
user=> (def results [1 2 3])
#'user/results
user=> (let [[light burnt just-right] results] (def light light) (def burnt burnt) (def just-right just-right))
#'user/just-right
user=> light
1
user=> burnt
2
user=> just-right
3

Differences between assoc-in with two elements and update / assoc

I've been doing for a while things like (assoc-in my-hash [:data :id] 1), and it looks fine.
Recently, since I rarely have more than two levels, I noticed I can do (update my-hash :data assoc :id 1), which sounds totally different, but returns the same.
So, I wonder, is there any difference in performance? Do you think it's more readable in one way than the other? More idiomatic?
update / assoc feels like it's more expensive to me, but I really like it better than assoc-in, which makes me stop to think each time I see it.
When it comes to performance, it's always good to measure. Ideally you'd assemble a realistic map (whether your maps are big or small will have some impact on the relative cost of various operations) and try it both ways with Criterium:
(require '[criterium.core :as c])
(let [m (construct-your-map)]
(c/bench (assoc-in m [:data :id] 1))
(c/bench (update m :data assoc :id 1)))
Under the hood, update + assoc is sort of the unrolled version of assoc-in here that doesn't need the auxiliary vector to hold the keys, so I would expect it to be faster than assoc-in. But (1) ordinarily I wouldn't worry about minor performance differences when it comes to things like this, (2) when I do care, again, it's better to measure than to guess.
(On my box, with Clojure 1.9.0-alpha14, update + assoc is indeed faster at ~282 ns vs ~353 ns for assoc-in given my small test map of (assoc (into {} (map #(vector % %)) (range 20)) :data {:id 0}).)
Ultimately most of the time readability will be the more important factor, but I don't think you can say in general than one approach is more readable than the other. If you have a → chain that already uses assoc-in or update multiple times, it may be preferable to repeat the same function for the sake of consistency (just to avoid making the reader wonder "is this thing really different"). If you have a codebase that you control, you can adopt a "house style" that favours one approach over the other. Etc., etc.
I might see assoc-in as a little more readable most of the time – it uses a single "verb" and makes it clear at a glance what the (single, exact) path to the update is – but if you prefer update + assoc and expect to keep their use consistent in your codebase, that's certainly fine as well.

Clojure: Validation in a Variadic function

Bearing in mind, “Programs are meant to be read by humans and only incidentally for computers to execute.”
I want to better understand how I can best express the intent of the argument(s) passed to a function.
For example, I have a very simple function:
user=> (defn add-vecs
"Add multiple vectors of numbers together"
[& vecs]
(when (seq vecs)
(apply mapv + vecs)))
Which in action will do the following:
user=> (add-vecs [1 2 3] [1 2 3] [1 2 3])
[3 6 9]
I don’t like relying on the doc-string to tell the reader of the code the requirements of the argument because comments often lie, developers don’t always read them and code changes but comments rarely do.
I could rename vecs something like "vecs-of-numbers" but that feels clumsy.
I appreciate Clojure is both dynamic and strongly typed and this question is clearly in the area of typing. So in general, and if possible some examples for this case, what would make the function argument(s) more readable?
When what you want is a type, use a type. There are several approaches to typing available. I'll use prismatic.schema in this case because it's fairly unimposing:
(require '[schema [core :as schema]]])
(schema/defn add-vecs [& vecs :- [Schema/Number]]
... code here ... )
or you can add a pre-condition to check it as runtime:

Clojure.typed basics

I've started playing with the seemingly quite impressive clojure.typed library, but very shortly after I run into problems, even when trying to apply it to simple functions. Does anyone have experience with the library?
Problem 1
(typed/ann square [Double -> Double])
(defn square "Square of"
[num]
(* num num))
Type Error (clojure_study/ideas/swarm/vector_algebra.clj:15:3) Return type of static method clojure.lang.Numbers/multiply is java.lang.Long, expected java.lang.Double.
Problem 2
(typed/defalias CartesianVector '{:x Double :y Double})
(typed/ann v+ [CartesianVector * -> CartesianVector])
(defn v+ "Sum vector of vectors"
[& vectors]
(apply merge-with + vectors))
Type Error (clojure_study/ideas/swarm/vector_algebra.clj:28:3) Bad arguments to polymorphic function in apply
in: (apply merge-with + vectors)
Problem 3
(typed/ann v- [CartesianVector CartesianVector -> CartesianVector])
(defn v- "Diff vector of vectors"
[v1 v2]
(merge-with - v1 v2))
Type Error (clojure_study/ideas/swarm/vector_algebra.clj:33:3) Polymorphic function merge-with could not be applied to arguments:
Polymorphic Variables:
k
v
Thanks for any help offered.
Your answer is now 3 years old, so this may not be much help, but I was using Typed Clojure in a big production codebase around the same time and have some experience with it. Also, the answers that weavejester provided in your Reddit thread on the topic are pretty much spot-on, so I'm just going to re-summarize them here to save future visitors the inconvenience of having to click another link.
In general your approach is correct at a high level but you're running into areas in which core.typed simply didn't (and maybe still doesn't) know how to behave smartly.
Here's what's going on:
Problem 1
This should probably be considered a bug on the behalf of core.typed, because there is a function signature supporting Double as a return type. You can circumvent this by using clojure.lang.Number or clojure.core.typed/Num instead, both of which encompass both Long and Double.
Problem 2
This is just a syntax error - that's not how you specify maps to core.typed. You should be using an HMap instead:
(t/defalias CartesianVector
(t/HMap :mandatory {:x t/Num, :y t/Num} :complete? true))
Problem 3
Unfortunately core.typed cannot successfully infer that merge-with (a core function) when applied to two maps of the same type will return a map of the same type. This is a limit of the type-checker. You can get around this by re-writing your function to explicitly merge rather than relying on merge-with:
(defn v-
[{x1 :x, y1 :y} {x2 :x, y2 :y}]
{:x (- x1 x2), :y (- y1 y2)})