plumatic / plumbing

Prismatic's Clojure(Script) utility belt

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for namespaced keys, part II

ikitommi opened this issue · comments

Hi,

Would like to have better support for namspaced keys in fnk-destructuring. Current status:

(= 1 (plumbing.core/letk [[a/b] {:a/b 1}] b))
; => true

which is great, but would like to support using namespaced keys for clarity as Clojure core support this:

(= 1 (let [{:keys [:a/b]} {:a/b 1}] b))
; => true

.. so, would like this to work:

(= 1 (let [[:a/b] {:a/b 1}] b))
; CompilerException java.lang.Exception: Unsupported binding form: :a/b

This would open open up the interoperability to clojure.spec as one could mix and match spec & schema annotations.

(defnk use-schemas [x :- s/Int, y :- s/Int] (+ x y))

(clojure.spec/def ::x integer?)
(clojure.spec/def ::y integer?)

(defnk use-spec [::x, ::y] (+ x y))

(defnk use-schema-and-spec [::x, y :- s/Int] (+ x y))

With current plumbing schema extraction helpers and a lookup to the clojure.spec registry, it one can post-resolve (on the client code) the types for namespaced keys. With a separate mapping of schema predicate to spec, one could also generate either typed schemas using specs or (maybe) specs using the schemas.

But I guess this would break the usage of keyworded keys identifying submaps? :(

; {:a {::x 1, ::y 2}}
[:a ::x ::y]

; {::a {::x 1, ::y 2}} or {::a 0, ::x 1, ::y 2}?
[::a ::x ::y]

Interesting. Yeah, it would break the sub-maps. It's also weird to me
that you can name something to be bound that's not a symbol -- it seems
like the real issue is that there's no parallel way to construct namespaced
symbols with aliases the same way as keywords?

I don't see an easy way to make this work without breaking something, or
introducing a weird custom syntax for namespaced symbols with aliasing,
e.g. !x or !alias/x, which I'm not in love with. Ideas?

On Fri, Jun 10, 2016 at 12:55 PM, Tommi Reiman notifications@github.com
wrote:

But I guess this would break the usage of keyworded keys identifying
submaps? :(

; {:a {::x 1, ::y 2}}
[:a ::x ::y]
; {::a {::x 1, ::y 2}} or {::a 0, ::x 1, ::y 2}?
[::a ::x ::y]


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#126 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAIPpjWKiz8wb9_RMFzgmgPeLZRn0kRlks5qKO49gaJpZM4Iynos
.

Thinking aloud: what if the default would be like it's today: [::a ::x ::y], would mean having a submap of ::a (with ::x and ::y). If one wants to say that all keys are in the same map, one could add meta-data to that vector to denote that. Would not break anything and would support both cases:

(-> [:a ^:keys [::x ::y ::z]] second meta)
; => {:keys true}

for top-level, it's bit hairy at the meta-data falls out of the arguments vector:

(defnk just-keys ^:keys [::x ::y ::z] ...)

Also, somehow related: http://dev.clojure.org/jira/browse/CLJ-1910

Eh, I'm wary of the idea of behavior changing based on metadata on the arg
vector.

What about using quote to indicate alias resolution, e.g.

(defnk just-keys ['x 'y 'alias/z] ...])

It currently errors so wouldn't break anything, and is at least somewhat
symmetric with ::x.

Thoughts?

On Fri, Jun 10, 2016 at 1:56 PM, Tommi Reiman notifications@github.com
wrote:

Thinking aloud: what if the default would be like it's today: [::a ::x
::y], would mean having a submap of ::a (with ::x and ::y). If one wants
to say that all keys are in the same map, one could add meta-data to that
vector to denote that. Would not break anything and would support both
cases:

(-> [:a ^:keys [::x ::y ::z]] second meta); => {:keys true}

for top-level, it's bit hairy at the meta-data falls out of the arguments
vector:

(defnk just-keys ^:keys [::x ::y ::z] ...)

Also, somehow related: http://dev.clojure.org/jira/browse/CLJ-1910


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#126 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAIPpi_CL0pkGGxwi0estrpVZEpY58Fsks5qKPypgaJpZM4Iynos
.

Thanks for the thoughts.

The quoted-syntax would work, but IMO it's bit (too) off from the current clojure syntax. Tried to figure out other ways to do this nicely, but all would require some special marker to define the "just keys" case. For (my) common case, this would not be a problem as sub-map keys are currently 100% not namespaced. But, spec might change this too.

Alex pointed me that the destructuring syntax is about to be modified (http://dev.clojure.org/jira/browse/CLJ-1919) in clojure, in contrast to that, few new suggestions:

Clojure

;; non-namespaced
(fn [{{{:keys [name sex age]} :body-params} :request}]
  (println "created user:" name sex age))

;; namespaced
(fn [{{{:keys [user/name user/sex user/age]} :body-params} :request}]
  (println "created user:" name sex age))

;; namespace-aliased
(fn [{{{:keys [::u/name ::u/sex ::u/age]} :body-params} :request}]
  (println "created user:" name sex age))

;; namespace-aliased (with CLJ-1919)
(fn [{{{::u/keys [name sex age]} :body-params} :request}]
  (println "created user:" name sex age))

Plumbing

;; non-namespaced (works already)
(fnk [[:request [:body-params name sex age]]]
  (println "created user:" name sex age))

;; namespaced (works already)
(fnk [[:request [:body-params user/name user/sex user/age]]]
  (println "created user:" name sex age))

;; namespaced (idea, CLJ-1919 -style)
(fnk [[:request [:body-params ^:user/keys [name sex age]]]]
  (println "created user:" name sex age))

;; namespace-aliased (idea, CLJ-1919 -style)
(fnk [[:request [:body-params ^::u/keys [name sex age]]]]
  (println "created user:" name sex age))

Is this too confusing to have the tagged sub-vectors? also, has about the same number of characters as the clojure version, just in the reverse order - which is good for readability, but more complex than the current plumbing syntax.

[:request [:body-params ^::u/keys [name sex age]]]
vs
{{{::u/keys [name sex age]} :body-params} :request}

Not 100% sure this is a good idea :) At least, the clojure core changes should to be finalised before doing anything. I'll keep this open, ok?

Thanks for the detailed writeup!

I'm fine with using metadata to indicate a prefix, just wasn't excited
about having it change the behavior of a first keyword from nesting to
namespace.

The name keys is a bit off since they are already always keys (it has a
different meaning in that sense than the Clojure version), so I'd also
consider something like ::user/ns, but I'm not sure if using a different
name to convey the difference in meaning is more or less clear than just
sticking to keys, what do you think?

There is also the question of how this plays with nested keys.

Does [a ^:foo/keys [:b [:c d]] bind {:a 1 :foo/b {:foo/c {:foo/d 2}}? Is
it allowed to override with a new namespace inside? Is there a way to turn
off the binding inside and go back to non-namespaced keys?

On Wed, Jun 15, 2016 at 2:07 PM, Tommi Reiman notifications@github.com
wrote:

Thanks for the thoughts.

The quoted-syntax would work, but IMO it's bit (too) off from the current
clojure syntax. Tried to figure out other ways to do this nicely, but all
would require some special marker to define the "just keys" case. For (my)
common case, this would not be a problem as sub-map keys are currently 100%
not namespaced. But, spec might change this too.

Alex pointed me that the destructuring syntax is about to be modified (
http://dev.clojure.org/jira/browse/CLJ-1919) in clojure, in contrast to
that, few new suggestions:
Clojure

;; non-namespaced
(fn [{{{:keys [name sex age]} :body-params} :request}](println "created user:" name sex age))
;; namespaced
(fn [{{{:keys [user/name user/sex user/age]} :body-params} :request}](println "created user:" name sex age))
;; namespace-aliased
(fn [{{{:keys [::u/name ::u/sex ::u/age]} :body-params} :request}](println "created user:" name sex age))
;; namespace-aliased (with CLJ-1919)
(fn [{{{::u/keys [name sex age]} :body-params} :request}](println "created user:" name sex age))

Plumbing

;; non-namespaced (works already)
(fnk [[:request [:body-params name sex age]]](println "created user:" name sex age))
;; namespaced (works already)
(fnk [[:request [:body-params user/name user/sex user/age]]](println "created user:" name sex age))
;; namespaced (idea, CLJ-1919 -style)
(fnk [[:request [:body-params ^:user/keys [name sex age]]]](println "created user:" name sex age))
;; namespace-aliased (idea, CLJ-1919 -style)
(fnk [[:request [:body-params ^::u/keys [name sex age]]]](println "created user:" name sex age))

Is this too confusing to have the tagged sub-vectors? also, has about the
same number of characters as the clojure version, just in the reverve order

  • which is good for readability, but more complex than the current syntax.

[:request [:body-params ^::u/keys [name sex age]]]
vs
{{{::u/keys [name sex age]} :body-params} :request}

Not 100% sure this is a good idea :) At least, the clojure core changes
should to be finalised before doing anything. I'll keep this open, ok?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#126 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAIPpmTktW4WMa9-iOi2vcXLz6r7n72Vks5qL5aNgaJpZM4Iynos
.

Another issue with namespaced keys that I've found is that you can't have bindings for two keys with the same name but different namespaces, e.g.:

[site/id :- :site/id
 user/id :- :user/id]

This will fail with "Binding is not valid" error.

I'd love to be able to use namespace aliases like this. Implementing spec in my project with namespaced keywords looks great everywhere except in my graph fnks, which have become horribly verbose.

The issue mentioned by @danielcompton is still a thorn in my side. It's really the one major problem that's preventing me from using graph.