plumatic / plumbing

Prismatic's Clojure(Script) utility belt
1.49k stars 108 forks source link

Support for namespaced keys, part II #126

Open ikitommi opened 8 years ago

ikitommi commented 8 years ago

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.

ikitommi commented 8 years ago

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]
w01fe commented 8 years ago

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 https://github.com/plumatic/plumbing/issues/126#issuecomment-225093103, or mute the thread https://github.com/notifications/unsubscribe/AAIPpjWKiz8wb9_RMFzgmgPeLZRn0kRlks5qKO49gaJpZM4Iynos .

ikitommi commented 8 years ago

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

w01fe commented 8 years ago

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 https://github.com/plumatic/plumbing/issues/126#issuecomment-225099981, or mute the thread https://github.com/notifications/unsubscribe/AAIPpi_CL0pkGGxwi0estrpVZEpY58Fsks5qKPypgaJpZM4Iynos .

ikitommi commented 8 years ago

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?

w01fe commented 8 years ago

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 https://github.com/plumatic/plumbing/issues/126#issuecomment-226096476, or mute the thread https://github.com/notifications/unsubscribe/AAIPpmTktW4WMa9-iOi2vcXLz6r7n72Vks5qL5aNgaJpZM4Iynos .

danielcompton commented 5 years ago

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.

WhittlesJr commented 5 years ago

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.

WhittlesJr commented 4 years ago

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.