This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-24
Channels
- # announcements (22)
- # babashka (33)
- # babashka-sci-dev (161)
- # beginners (25)
- # calva (57)
- # cider (6)
- # clara (6)
- # clerk (14)
- # clj-kondo (24)
- # clojars (10)
- # clojure (65)
- # clojure-austin (1)
- # clojure-conj (2)
- # clojure-europe (23)
- # clojure-miami (3)
- # clojure-nl (3)
- # clojure-norway (3)
- # clojure-uk (3)
- # clojurescript (28)
- # cursive (24)
- # datomic (136)
- # emacs (38)
- # graalvm (29)
- # graphql (3)
- # introduce-yourself (8)
- # jackdaw (4)
- # jobs-discuss (9)
- # malli (5)
- # nbb (36)
- # off-topic (11)
- # pathom (58)
- # polylith (2)
- # practicalli (1)
- # re-frame (5)
- # reagent (11)
- # releases (1)
- # remote-jobs (8)
- # sci (15)
- # shadow-cljs (31)
- # slack-help (2)
- # spacemacs (11)
- # sql (7)
- # tools-build (9)
My brain is choking on finding a way to do this:
input: (1 2 :nest 3 4 :nest 5 6 7 :nest 8)
output: (1 2 (3 4 (5 6 7 (8))))
Write a function that takes above input and returns above output?Put everything in a stack, build the sequence up as you pop it Or use recursion and build it up on return Remember recursive functions can do interesting things post call
Erm, I was thinking of a stack, haha, but it's not coming to me. I'll think a bit more.
seems like zippers would allow a pretty straightforward implementation. you can use zip/edit
+ zip/rights
when you encounter a :nest
.
Feel free to try it, this isn't some exercise question I'm looking for help with, but code I need for a macro I'm trying to write 😛 So I don't mind spoilers on the solution.
(defn nest-seq [coll]
(when (seq coll)
(let [[f & r] coll]
(if (= f :nest)
(list (nest-seq r))
(cons f (nest-seq r))))))
(nest-seq '(1 2 :nest 3 4 :nest 5 6 7 :nest 8))
=> (1 2 (3 4 (5 6 7 (8))))
Ok, also got the "imperative" one working:
(let [stack (atom '())
new-list (atom '())
input '(1 2 :nest 3 4 :nest 5 6 7 :nest 8)]
(doseq [e input] (swap! stack conj e))
(while (seq @stack)
(let [e (ffirst (swap-vals! stack pop))]
(if (not= :nest e)
(swap! new-list #(cons e %))
(do (swap! stack conj @new-list)
(reset! new-list '())))))
@new-list)
(defn remove-rights [zip]
(assoc-in zip [1 :r] nil))
(defn nest [xs]
(loop [zip (z/zipper seqable? seq
#(do %2)
xs)]
(if (z/end? zip)
(z/root zip)
(let [zip (if (= :nest (z/node zip))
(let [rights (or (z/rights zip)
'())]
(-> zip
remove-rights
(z/replace rights)
z/next))
zip)]
(recur (z/next zip))))))
in comparison with the seq
version, this will also work with existing nesting and trailing :nest
s:
> (nest '(1 2 (:nest 3 4) :nest 5 6 7 :nest 8 (:nest)))
;; (1 2 ((3 4)) (5 6 7 (8 (()))))
For completeness, recursive version for nested seq:
(defn nest-seq [coll]
(if (empty? coll) '()
(let [[f & r] coll]
(cond
(sequential? f) (cons (nest-seq f)
(nest-seq r))
(= f :nest) (list (nest-seq r))
:else (cons f (nest-seq r))))))
You could do it with reduce
by inputting the reversed collection
(defn nest-seq
[coll]
(reduce (fn [acc curr]
(if (= :nest curr)
(list acc)
(cons curr acc)))
'()
(reverse coll)))
(nest-seq '(1 2 :nest 3 4 :nest 5 6 7 :nest 8)) ;; => (1 2 (3 4 (5 6 7 (8))))
Ok, I think I got the hang of the zippers, pretty neat:
(let [zip (zip/seq-zip '(1 2 :nest 3 4 :nest 5 6 7 :nest 8))]
(loop [zip (zip/down zip)]
(cond (= (zip/node zip) :nest)
(recur
(zip/down
(assoc-in
(zip/edit zip #(rest (cons % (zip/rights zip))))
[1 :r] nil)))
(zip/right zip)
(recur (zip/right zip))
:else
(zip/root zip))))
The other benefit of zippers is that they don’t consume the stack, so you can nest large structures without stack overflows
is it possible to def a multimethod that dispatches on the result of a function call? e.g. (defmethod a-multimethod #(dispatch-f %) [v] (do-stuff))
when dispatch-f
should be called?
I would like it to be called when I pass in an argument to a-multimethod
which I will update my code to reflect
But it doesn't seem to work that way, leading me to think that it's not possible.
it looks like an example of case
(case (dispatch-f arg)
"val1" (do-stuff)
"val2" (do-stuff))
so I could use that case
expr in place of #(dispatch-f %)
here?
is there a reason you can't use dispatch-fn
on your defmutli
call? are you trying to dispatch your defmethod
s on dynamic values?
typically, how you'd set up multi-methods like this:
(defn dispatch
[v]
:foo)
(defmulti foo #'dispatch)
(defmethod foo :foo
[v)
(do-stuff))
defmethods match on values. defmulti can use any function to generate those values.
so in essence, I have one multi-method that is conforming a map (one of a set of possible valid maps) to a spec, and then another multi-method that is giving me back only valid maps. I am concerned that this is a totally harebrained way to do it after thinking about it for awhile.
> so I could use that case
expr in place of #(dispatch-f %)
here?
instead of defmulti for a-multimethod you can use normal function that internally uses (case (dispatch-f arg) ,,
expression
The problem I am running into is that conform
returns either :clojure.spec.alpha/invalid
or the conformed data, and I can't see how to def a method that works on my conformed data
the :invalid
part is easy
Thanks @U2FRKM4TW. I looked at :default
but ended up taking one "layer" out of my multi-method hierarchy and got it to work as expected.
Hi guys I have doubts on how to convert a json file to a Clojure map using spec. I already have my spec structure defined and I have managed to read the json I need to convert. The challenge is to do the following (s/valid? invoice invoice) ; => true I don't know how to start getting there, i.e. should I read every object in my json file and convert it to a map, how would I implement my spec structure for that? Thank you very much for your help
invoice-spec.clj
invoice.json
You need a step to transform the parsed json into the expected shapes you have spec'd. You can use the data.json
library's key-fn
conversion options to help with this: https://github.com/clojure/data.json#converting-keyvalue-types
It might be easier if you use :req-un
on your s/keys
specs instead of :req
so that you can just translate the key to the correct non-qualified key, so that you don't have to have context-awareness when you're doing this initial transformation.
Are you the same person who asked this on Reddit? There I recommended looking at e.g. https://github.com/exoscale/coax which allows you to coerce string values to other types via clojure.spec.alpha. The steps to follow here are: 1. Transform the JSON string to a Clojure data structure (with keywordized keys) 2. Then run that value through coax cc @U050SC7SV
[@U04V15CAJ side note but for some reason I don't see your response on the reddit post] edit: nevermind there's one from yesterday and today
there is also metosin/spec-tools that leverages coersion from json via spec https://github.com/metosin/spec-tools/blob/master/docs/01_coercion.md
Here is a REPL session in babashka:
$ rlwrap bb -Sdeps '{:deps {exoscale/coax {:mvn/version "1.0.0"}}}'
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::my-map (s/keys :req-un [::foo]))
:user/my-map
user=> (s/def ::foo int?)
:user/foo
user=> (require '[exoscale.coax :as c])
nil
user=> (c/coerce ::my-map {:foo "1"})
{:foo 1}
user=> (require '[cheshire.core :as json])
nil
user=> (json/parse-string "{\"foo\": \"1\"}" true)
{:foo "1"}
user=> (->> (json/parse-string "{\"foo\": \"1\"}" true) (c/coerce ::my-map))
{:foo 1}
not sure how maintained spec-tools is nowadays (no clue really, but since it's from the authors of malli maybe they moved on)
spec-tools is also a part of well maintained reitit library
all this should be done in the same invoice-spec.clj file where I have the whole structure ready? Or I could call it from another file to perform the function I need, since my goal is to create a function that receives the name of the json file and return the map and then do the validation (s/valid?).
I understand, so in order to do this I should import the invoice-spec file and require spec to be able to do it from another file?
as long as you load the namespace so the spec is defined. specs are defined by keywords in a global registry
Just to validate ::order corresponds to the spec structure order corresponds to the function?
It might be good to read up on: • https://clojure.org/guides/weird_characters#autoresolved_keys • https://clojure.org/guides/spec
You don't have to use ::foo
, you can also write:
(s/def :my-foo/bar int?)
::
is just a notation to get auto-resolved keywords which either refer to the current namespace or another one via an aliasAnd a spec can be either a predicate or a complex object defined by any of the spec ops. A keyword is used to identity the spec by name.
Woow This is impressive, many doubts have been cleared up for me. Thank you very much for your support, I am new to Clojure and I appreciate your time.
(ns problem_02
(:require
[clojure.data.json :as json]
[clojure.spec.alpha :as s]
[invoice-spec :as invoice]))
(defn invoice
[name_json]
(let [ file_json (json/read ( name_json))
write_json (json/write-str file_json)]
(json/read-str write_json :key-fn keyword)
))
I have created the following code
Basically what I do is
1. Call the .json with Reader
2. I convert the json to a string using write-str
3. I read that string to be able to convert it to map thanks to key-fn
I have revised the invoice-spec.clj structure by adjusting the field names to match the json structure and the data types
When performing the validation I get false, does anyone know why this happens? Should I take more things into account?@U04L0R4MVCN You can do (s/explain ::invoice/invoice ...)
to see what's wrong
If you use https://github.com/bhb/expound you will it in a more human-readable way
I don't understand this error, the json file has these keys as well as invoice-spec.
I was checking the keys and actually key-fn did its job well, it should recognize the keys it says spec not recognize.
Woow This is impressive, many doubts have been cleared up for me. Thank you very much for your support, I am new to Clojure and I appreciate your time.