redplanetlabs / specter

Clojure(Script)'s missing piece
Apache License 2.0
2.51k stars 102 forks source link

Error: Not a navigator: [object Object] cljs.core/MetaFn #312

Open eneroth opened 3 years ago

eneroth commented 3 years ago

I'm trying to write a function to replace all instances of a value in a data structure with another value.

Specter is black magic to me, so I started from an example and ended up with this:

(defn deep-replace
  "Given a nested data structure and two values, finds instances of the first
   value and replaces it with the second value.

   Will replace keys."
  [data from to]
  (let [walker (sp/recursive-path [] p
                 (sp/cond-path
                   map?
                   (sp/multi-path
                     [(sp/map-key from)]
                     [sp/MAP-VALS
                      (sp/if-path [(sp/pred= from)]
                        sp/stay-then-continue
                        [p])])
                   sequential?
                   [sp/ALL
                    (sp/if-path [(sp/pred= from)]
                      sp/stay-then-continue
                      [p])]))]
    (sp/setval walker to data)))

It works as I expect in Clojure,

(deep-replace [2 3 4 5 [6 7 8 {:a 2 :b [2 {:c 2 :d 10}]}]
               {#inst "2017" 2}
               {2 2}
               {2 {2 3}}]
  2 "Replaced")

Yields,

["Replaced"
 3
 4
 5
 [6 7 8 {:a "Replaced", :b ["Replaced" {:c "Replaced", :d 10}]}]
 {#inst "2017-01-01T00:00:00.000-00:00" "Replaced"}
 {"Replaced" "Replaced"}
 {"Replaced" {"Replaced" 3}}]

However, in ClojureScript, I get

Error: Not a navigator: [object Object] cljs.core/MetaFn

I thought my data structures in ClojureScript contained something surprising, but it's the case for this as well:

(deep-replace [] 2 "Replaced")

Am I doing something weird in the above function?

nathanmarz commented 3 years ago

This looks to be a bug. The fix might be as simple as extending the ImplicitNav protocol to MetaFn like is done already to function. You should be able to try doing that in your own project and see if it fixes it.

WorldsEndless commented 2 years ago

I have the same error, doing basically the same thing (trying to replace all keyword values within an arbitrarily nested structure. I'm working in CLJC, and it works on the CLJ side but not the CLJS.

WorldsEndless commented 2 years ago

This looks to be a bug. The fix might be as simple as extending the ImplicitNav protocol to MetaFn like is done already to function. You should be able to try doing that in your own project and see if it fixes it.

I have searched in vain for the example you are talking about, in which the implicitnav function is extended. Can you link where that is done, @nathanmarz ?

nathanmarz commented 2 years ago

@WorldsEndless https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter.cljc#L1252

WorldsEndless commented 2 years ago

I'm a newbie to programming with protocols and types, so feel pretty clumsy. I can't locate a definition of pred in this, and have scoured the namespace without success:

(extend-type #?(:clj clojure.lang.AFn :cljs cljs.core/MetaFn)
  com.rpl.specter.protocols/ImplicitNav
  (implicit-nav [this] (pred this)))
WorldsEndless commented 2 years ago

Ah, found pred. https://github.com/redplanetlabs/specter/blob/d0d6fdf5818c6b3d939c4f9dfc670e79b3563f3d/src/clj/com/rpl/specter.cljc#L1210

WorldsEndless commented 2 years ago

adding the following did indeed fix the issue, so my function now works in both clj and cljs:

(ns foo
  (:require [com.rpl.specter :as s]
            [com.rpl.specter.protocols]))

 (extend-type #?(:clj clojure.lang.AFn :cljs cljs.core/MetaFn)
    com.rpl.specter.protocols/ImplicitNav
    (implicit-nav [this] (s/pred this)))
dmg46664 commented 2 years ago

Got the following error in CLJS when the path wasn't in a vector version 1.1.4. Thought I'd report it as it's not very clojurey to care that it isn't a vector, but was easily fixed in project code.

      #error {:message "Not a navigator",
              :data {:this
                     (:my/cool
                      "key"),
                     :type-str "cljs.core/Cons"}}