Fork me on GitHub
#clojure
<
2024-01-02
>
joshcho00:01:34

Is it possible to create a map where access via keys other than specified few result in error?

joshcho01:01:18

Safer maps essentially

hiredman01:01:33

it is possible, it is all interfaces and you just implement the right ones and you can make all kinds of weird map types. if that is safer or not is debatable

hiredman01:01:55

it is almost always this case that you want to specify that a certain subset of information is present, but allow for more information than that to be present (this is the basis for things like sub typing in static type systems, etc)

hiredman01:01:49

which is basically the opposite of what you are suggesting

joshcho01:01:35

sure, but often for modeling domains this additional capability is less value than ability to catch incorrect use of the interface

joshcho01:01:52

also presumably this is a one-line change if the map changes

seancorfield01:01:11

(get my-map :unknown-key ::not-found) => ::not-found so you can check for it that way, or (contains? my-map :some-key) => false if the key isn't in the map. Clojure Spec can be good for validation around the edge of your domain -- but it's design, along with Clojure's design, is that maps should be open for extension so additional keys should be accepted (and ignored, if they aren't "useful"). For specific situations where you require no additional keys, you can either select-keys to get the known subset you want or check (set (keys my-map)) against a valid set of keys.

ghadi01:01:26

this desire creates inflexible/brittle programs

💯 2
1
☝️ 1
phill02:01:02

If you are open-mindedly desperate for bad ideas, then another thing you can do is adopt the regimen of referring to map entries not by keyword but by var. That is, (def somekey :somekey) and thereafter use somekey as the map key. If you misspell it, the compiler will tell you. But, on balance, wouldn't it be better to focus on the positive - if the program is using a wrong keyword, and yet all the tests pass, then evidently the program is working fine and it's not a problem.

hiredman02:01:32

Or just assert that the maps contain the keys you are interested in and move on

hiredman02:01:49

Typos will fail because the key will always be missing

hiredman02:01:06

(defn g [m k] (if-let [[k v] (find m k)] v (assert nil)))

hiredman02:01:54

(of course throw something useful with ex-info, I am just really lazy about using assert nil when I want to turn a branch into an exception)

joshcho08:01:34

thank you for the ideas everyone. so in essence, no library exists that does this? or any resources as to how to implement such a thing

reefersleep09:01:35

Maybe something like this could be helpful? https://blog.wsscode.com/guide-to-custom-map-types/

❤️ 1
joshcho09:01:00

oh i forgot about that! yeah that’s helpful ty

🍻 1
seancorfield15:01:26

> no library exists that does this It's more that what you're wanting to do is probably a bad idea and goes against the design principles of Clojure 🙂

1
1
☝️ 1
vemv15:01:29

Wanting some degree of safety is perfectly fine though, there isn't a single programming model / set of guidelines that will satisfy each and every Clojure team out there. An unsuspected solution is to roll your own get / get-in and ban their clojure.core counterparts https://github.com/clj-kondo/clj-kondo/blob/be374eac1814c0d98c4fa6616c24a84478ce588c/doc/linters.md#discouraged-var. This technique sounds safer to me than the bespoke map, since it works with every map: • You don't have to remember using the custom map each time • You can freely use assoc, dissoc, or similar functions which might discard the custom map and return a vanilla one

❤️ 1
seancorfield15:01:53

☝️:skin-tone-2: Plus also ban (my-map some-key) and (:a-key my-map) and using keywords as functions, e.g., (map :a-key coll-of-maps) -- not sure how enforceable that would be automatically...?

ghadi15:01:36

'safety' is the semantic that needs to be defined here

💯 2
vemv16:01:03

Map-as-function sounds the hardest 🙂 Personally get-in is the most interesting function to add safety to anyway, since nested paths quite often break with refactorings or similar changes

seancorfield16:01:39

(I will note that in 13 years of production Clojure work and now with 140k lines of Clojure, I have never felt the need for such a data structure 🙂 -- select-keys, contains?, and Spec have always been sufficient to ensure "bad" keys don't end up where they should not be... wow, Spec has been around for almost eight years now! May 24th, 2016 was the first alpha version!)

joshcho05:01:08

thanks @U45T93RA6 , that sounds like a much simpler workaround to test this out

David G18:01:21

How do you submit a bug for clojure.data.json (or at least I think it's a bug/should fail on assertion)?

(require '[clojure.data.json :as json])
(json/write-str {:a 1 "a" 2})
;; => "{\"a\":1,\"a\":2}"

👀 2
phronmophobic18:01:01

Usually, the best place is to submit a question on https://ask.clojure.org/. You can also check if it's already been addressed before.

👍 1
David G18:01:32

I'll register in a few and submit it, thanks for checking!

Alex Miller (Clojure team)19:01:17

json keys are always strings, keywords and symbols use the symbol/keyword name by default, everything else stringifies (strings are of course always strings). you can overload :key-fn if you want other behavior

David G19:01:19

The docstring there only addresses the default behavior of keyword->name, unless I'm missing something

Alex Miller (Clojure team)19:01:58

"Default calls clojure.core/name on symbols and keywords and clojure.core/str on everything else." covers both ?

David G19:01:16

How does that address that the output containing duplicate strings? It's on the user to know the default behavior outputting a string will produce an invalid json in this case?

Alex Miller (Clojure team)19:01:17

oh, I didn't even notice that aspect of your example :) but yes, that's going to be on you. it's unusual to have a Clojure map with both keyword and strings (which may overlap), so I think you'd want to apply some extra cleanup before json-ifying

Alex Miller (Clojure team)19:01:41

the cost of doing that for all writes would be high in data.json

David G19:01:28

Should I register this as a question for others to see/know about? I know Slack also gets indexed for search

Alex Miller (Clojure team)19:01:53

your future LLM thanks you :)

😆 1
delaguardo07:01:18

strictly speaking, duplicate keys are not wrong according to the json specification.

🙀 1
zimablue19:01:40

is there any built in way or library to relate pprint output location to edn paths? eg. if (= (with-out-str (pprint {:a [1 2 3]})) "{:a \n[1 2 3]}") then (= (imaginary-fn {:a [1 2 3]} [:a 2]) [10 11]) so converting from the edn-path for get-in of the first fn (or some other data-oriented path) to the index of the characters representing it in the output?

phronmophobic19:01:36

For examples, you can check out neil which will can update a deps.edn file while preserving formatting, https://github.com/babashka/neil/blob/main/src/babashka/neil.clj

zimablue19:01:50

thanks, I was just looking at zprint which is built on top of it, thinking about it pprint is kind of part of a linter

phronmophobic19:01:31

I guess neil is built on https://github.com/borkdude/rewrite-edn which uses rewrite-clj under the hood

zimablue19:01:05

sorry meant on top of rewrite-clj yeah. I think that, cljfmt or zprint