Issue with traverse
Gonzih opened this issue · comments
Hello, I'm playing around with this library and I got stuck trying to reproduce traverse example.
(defrecord Country [iso-id]
DataSource
(fetch [_] (api-get (str "countries/" iso-id))))
(defrecord Region [country-iso-id url-id]
DataSource
(fetch [_] (api-get (str "countries/" country-iso-id "/" url-id))))
(->> (Country. "es")
(fmap :regions)
(traverse #(Region. "es" (:code %)))
run!!)
With this code snippet I'm getting folowing error:
Exception in thread "async-dispatch-26" java.lang.IllegalArgumentException: No implementation of method: :done? of protocol: #'muse.core/MuseAST found for class: muse_playground.core.Region
Am I missing something here? Should I implement some additional protocols on my records?
Thanks!
Unfortunately, I can't reproduce it:
user=> (require '[muse.core :refer :all])
nil
user=> (require '[clojure.core.async :as a])
nil
user=> (defrecord Country [iso-id] DataSource (fetch [_] (go {:regions [{:code 1} {:code 2} {:code 3}]})))
user.Country
user=> (defrecord Region [country-iso-id url-id] DataSource (fetch [_] (a/go (inc url-id))) LabeledSource (resource-id [_] url-id))
user.Region
user=> (run!! (->> (Country. "es") (fmap :regions) (traverse #(Region. "es" (:code %)))))
[2 3 4]
user=> (->> (Country. "es") (fmap :regions) (traverse #(Region. "es" (:code %))) run!!)
[2 3 4]
Note, that I added LabeledSource
to Region
, but it doesn't make a difference with respect to exception that you've got.
Can you, please, provide more information:
- version of
muse
,clojure
andcore.async
that you use - import statement from current file
@Gonzih I ran few more tests. And in few scenarios when cache id = nil (as you have) it can throw IllegalArgumentException
exception. As it's non-deterministic and hard to debug, I will update library to use explicit assert
that both resource name and resource ID are resolvable.
In you case, muse
library can calculate names: Country
& Region
, but IDs are not specified.
You can fix this as following:
user=> (defrecord Country [iso-id] DataSource (fetch [_] (go {:regions [{:code 1} {:code 2} {:code 3}]})) LabeledSource (resource-id [_] iso-id))
user.Country
user=> (defrecord Region [country-iso-id url-id] DataSource (fetch [_] (a/go (inc url-id))) LabeledSource (resource-id [_] (str country-iso-id "//" url-id)))
user.Region
or declare both records with an ID attr:
user=> (defrecord Country [id] DataSource (fetch [_] (go {:regions [{:code 1} {:code 2} {:code 3}]})))
user.Country
user=> (defrecord Region [country-iso-id id] DataSource (fetch [_] (a/go (inc id))))
user.Region
Yes, I was able to figure out that resources in traverse are cached very agressively. LabaledResource seems to be working, thanks! Aparetntly I wasn't reading readme very carefuly :) Sorry about that.
Also small followup question; Is it possible to use result of traverse in cats.core/mlet?
@Gonzih, like this?
user=> (run!! (m/mlet [regions (->> (Country. "es") (fmap :regions) (traverse #(Region. "es" (:code %))))] (m/return (count regions))))
3
Not exactyl, something like that:
(run!! (m/mlet [country (Country. "es")
full-regions (traverse #(Region. (:code country)
(:code %))
(:regions country))]
(m/return (assoc country :regions full-regions))))
Still can't figure out how to use traverse and preserve original country monad to reuse it later.
That's a bit tricky... You have to know really well what's going on inside mlet
to deal with it:
user=> (defrecord Country [id] DataSource (fetch [_] (go {:regions [{:code 1} {:code 2} {:code 3}]})))
user.Country
user=> (defrecord Region [country-iso-id id] DataSource (fetch [_] (go (inc id))))
user.Region
user=> (run!! (m/mlet [country (Country. "es")
regions (collect (map #(Region. "es" (:code %)) (:regions country)))]
(m/return (count regions))))
3
The problem with traverse
is that is makes flat-map
which is not an option inside mlet
as you work with "pure" value.
@Gonzih does it make sense? Or should I provide further explanation?
Yes, this makes sense. My last question is how collect works?
Thanks for your patience :)
On 07/10/2015 09:27 AM, Alexey Kachayev wrote:
@Gonzih https://github.com/Gonzih does it make sense? Or should I
provide further explanation?—
Reply to this email directly or view it on GitHub
#4 (comment).
collect
takes a seq of MuseAST nodes (or resources to fetch) and convert them to a single MuseAST node. Definition with scala
type system will look like following:
def collect[A](fs: Seq[MuseAST[A]]): MuseAST[Seq[A]]
Makes sense now, thank you!
On 07/10/2015 10:17 AM, Alexey Kachayev wrote:
|collect| takes a seq of MuseAST nodes (or resources to fetch) and
convert them to a single MuseAST node. Definition with |scala| type
system will look like following:def collectA: MuseAST[Seq[A]]
—
Reply to this email directly or view it on GitHub
#4 (comment).
You are welcome!