using clojure spec for dispatching on different map structures

165 views
Skip to first unread message

Brent Millare

unread,
Sep 13, 2017, 12:54:31 PM9/13/17
to Clojure
I have several maps with different combinations of keys for each map. I want to process each map but do different work depending on the set of keys available, basically dispatch on key availability. I thought clojure.spec might be a good fit for doing the classification step. So for each key, I could define a spec. Next, I would use clojure.spec.alpha/keys to define a spec for each set of keys I'd like to match. Finally, I would dispatch like so:

(fn [m]
  (condp clojure.spec.alpha/valid? m
      ::foo-map-spec :do-foo-stuff
      ::bar-map-spec :do-bar-stuff))

Does this seem reasonable?

The advantage in my mind is its thorough, explicit, and easy to read. Possible downsides is performance if it mattered.

What are the advantages/disadvantages to this approach compared to other methods?

Alex Miller

unread,
Sep 13, 2017, 3:46:43 PM9/13/17
to Clojure
You might want to look at s/multi-spec which lets you create a variable open spec based on a multimethod, which would in this case be based on key availability.

Brent Millare

unread,
Sep 13, 2017, 4:47:40 PM9/13/17
to Clojure
The example for multi-spec provided in the spec guide assumes a key that denotes a "type". (This key is used as the dispatch fn). In my situation, I'm assuming I don't have control over what maps are generated. In other words, I explicitly do not want to have to encode types into the input map. Instead, I want to rely on the "shape" (key set), since that is the most flexible. While I could create a dispatch function that analyzes the input and produces a "type", that offers no advantages over what I presented earlier. Adding more cases is not extensible since it requires redefining the dispatch fn. In other words, I'd be just copying pasting my presented function as the dispatch fn.

Alex, did I address your point? Is the approach you are suggesting different?

Alternatively, my previous example might have been misleading since I used keys to represent useful work. Perhaps it would have been better written as:

(fn [m]
  (condp clojure.spec.alpha/valid? m
      ::foo-map-spec ... do foo stuff ...
      ::bar-map-spec ... do bar stuff ...))

Alex Miller

unread,
Sep 13, 2017, 4:55:44 PM9/13/17
to Clojure
Seems pretty slow to dispatch on a linear series of spec validations, but I don't see any reason it wouldn't work.

Brent Millare

unread,
Sep 13, 2017, 5:11:33 PM9/13/17
to Clojure
Yeah, that's what I thought. I guess I'm using it in a way that's not standard practice. Luckily it's not a bottleneck in my case.

Has there been discussion about a "shortable" version of s/keys which just checks for key existence and doesn't recursively validate the corresponding values?

I suppose if performance were an issue assuming the same requirements, I'm guessing some minimal work key-checking algorithm exists that I could write. For now, clojure.spec is convenient.

Alex Miller

unread,
Sep 13, 2017, 7:09:29 PM9/13/17
to Clojure
I don't see any benefit of using a subset of spec to do this - why not just use normal Clojure code on sets of keys?

Didier

unread,
Sep 13, 2017, 11:05:54 PM9/13/17
to Clojure
I'm not following 100%. But s/or of specs will comform to the first valid spec it encounters. It can be used for duck typing, aka, strutural types.

So given you have many maps, each can be a spec. Then you can create a spec which is one of these shapes of maps. Pass any map to the or spec, and it'll tell you the first one that validated against the map, returning its name and comformed value.

Reply all
Reply to author
Forward
0 new messages