beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
2021-04-09T01:19:01.295600Z

I'd just use a map, possibly even just a vector

1👍
CĂ©lio 2021-04-09T01:27:29.296400Z

Hi all. I’m not sure whether this is the right forum to ask this question (this is going to be a long post) so please feel free to point me out to the right place. I was looking for a function to “extract” values from different places deep inside a map, using something that resembles pattern matching (doesn’t need to be pattern matching though). For example suppose you have this map copied from some deps.edn file:

(def deps {:deps {'acme/spaceship {:mvn/version "1.0.0"}
                  'org.clojure/clojure {:mvn/version "1.10.1"}}
           :aliases {:moon {:extra-deps {'moon/lander {:mvn/version "0.8.7"}
                                         'rock/examiner {:mvn/version "4.0.21"}}
                            :override-deps {'foo/bar {:mvn/version "1.2.3"}}}
                     :mars {:extra-deps {'mars/rover {:mvn/version "1.14.9"}
                                         'comms/serial {:mvn/version "2.4.2"}}
                            :default-deps {'hello/world {:mvn/version "9.8.7"}}}}})
I want a list of all deps, including deps that are deep inside aliases, plus their respective locations (or paths) inside the map. So my first question is: is there such function available somewhere? I couldn’t find such a function, so I made one. Here’s how it works using the example above. I’ll first define a list of “patterns” used to locate the deps:
(def patterns [[:deps]
               [:aliases keyword? #{:extra-deps :override-deps :default-deps}]])
I’ll try to explain how those patterns work but I think you probably already get the idea. Here we have two patterns. Patterns are used to match paths to values inside the map, so if a value exists in a path that matches any of the patterns, the value is returned along with its path. Each element in the pattern can be: ‱ A keyword ‱ A value ‱ Or something that implements IFn If it’s a keyword or something that doesn’t implement IFn, it’s tested for equality. Otherwise it’s called as a predicate function on the map’s keys. Now I call the extract function like this:
(extract patterns deps)
And it returns this list:
({:path [:deps], :val {acme/spaceship {:mvn/version "1.0.0"}, org.clojure/clojure {:mvn/version "1.10.1"}}}
 {:path [:aliases :moon :extra-deps], :val {moon/lander {:mvn/version "0.8.7"}, rock/examiner {:mvn/version "4.0.21"}}}
 {:path [:aliases :moon :override-deps], :val {:foo/bar {:mvn/version "1.2.3"}}}
 {:path [:aliases :mars :extra-deps], :val {mars/rover {:mvn/version "1.14.9"}, comms/serial {:mvn/version "2.4.2"}}}
 {:path [:aliases :mars :default-deps], :val {:hello/world {:mvn/version "9.8.7"}}})
Here’s the implementation:
(defn- key-matches?
  [p k]
  (if (or (keyword? p) (not (ifn? p)))
    (= p k)
    (p k)))

(defn- extract-p
  [pattern val path]
  (let [p (first pattern)
        ks (filter #(key-matches? p %) (keys val))]
    (if (seq pattern)
      (map (fn [k]
             (extract-p (rest pattern)
                        (get val k)
                        (conj path k)))
           ks)
      {:path path :val val})))

(defn extract
  ([patterns m]
   (flatten
    (map (fn [pattern] (extract-p pattern m [])) patterns))))
Now my second question is, do you see any problems with this implementation, or any opportunities for improvements? I think my biggest concern is how it nests calls to map, which is why I use flatten at the end. I wonder if this could result in a significant performance penalty?

2021-04-09T10:54:01.314800Z

something like this? https://github.com/DotFox/matchete/blob/main/test/dotfox/matchete/ref_test.cljc#L33-L61

1👍
CĂ©lio 2021-04-09T12:13:39.315200Z

Something like that. Thanks!

2021-04-09T12:37:59.315400Z

This library is still under develop but feel free to have a look at implementation

CĂ©lio 2021-04-09T13:38:41.318200Z

@delaguardo Very interesting. I’m trying to understand how the registry works (the first argument passed to m/matcher).

2021-04-09T13:41:54.318400Z

https://github.com/DotFox/matchete/blob/main/src/dotfox/matchete/compiler.cljc#L287-L292 first argument is a map of identifier -> pattern which you can use later in the pattern given as a second argument for m/matcher wrapped as a [:ref id] sorry for such bloated explanation ) I’m still working on proper documentation for the lib

2021-04-09T13:44:11.318700Z

default-registry function places ::custom (atom {}) as a container for every custom patterns you might want to bring in as a first argument for m/matcher. And during compilation every pattern like [:ref id] will use this atom to look up a matcher to use

seancorfield 2021-04-09T01:29:19.296700Z

Take a look at Meander and Specter.

1👍
seancorfield 2021-04-09T01:30:11.297600Z

Both of those are designed to help you navigate deeply nested maps.

CĂ©lio 2021-04-09T01:30:36.298Z

Thanks a lot, I’ll take a look!

grazfather 2021-04-09T02:05:55.299300Z

are there some examples of just
 really exemplary clojure code that makes a good read?

1🙏
seancorfield 2021-04-09T02:12:17.300200Z

I don’t know. A lot of library code has to do “weird” stuff (which is why the library exists), so I suspect most of the “exemplary” Clojure code is in proprietary applications


seancorfield 2021-04-09T02:14:49.301900Z

I certainly wouldn’t say any of my OSS projects are “exemplary” code since they mostly have to jump through hoops to get their jobs done. I’m not sure I’d say our code base at work is “exemplary” either but some parts of it are nice (and some parts are
 not so nice) out of 113k lines


seancorfield 2021-04-09T02:15:42.302500Z

(but then it spans a decade of learning Clojure followed by adopting various idioms over time)

blak3mill3r 2021-04-09T05:33:56.304200Z

I agree with @seancorfield that it depends on what kind of exemplary you're after, but I will offer an opinion that this library is some of the finest Clojure that I've seen: https://github.com/sicmutils/sicmutils

2👍
blak3mill3r 2021-04-09T05:34:31.304800Z

That does not mean that it is particularly easy to grok, just that it is very well thought out

seancorfield 2021-04-09T06:06:53.305300Z

True, there’s a big difference between “well-designed” and “easy-to-read”.

3💯
grazfather 2021-04-09T13:38:28.318Z

I guess I’ll take a bit of both. Thank you both.

popeye 2021-04-09T07:11:50.306300Z

how to fetch the value from Variadic Functionsin clojure

popeye 2021-04-09T07:12:02.306600Z

(defn hello [greeting & who]
  (println greeting who))

popeye 2021-04-09T07:12:14.306900Z

how to get individual values from who?

yuhan 2021-04-09T07:15:51.308500Z

They're regular sequences, you can use any core function like first , rest, nth , or destructure it further, or map over it etc.

Rolfe Power 2021-04-09T07:15:58.308800Z

It's a sequence

tvirolai 2021-04-09T07:16:12.309300Z

You could do something like this: (defn hello [greeting & [firstarg secondarg]] ...)

yuhan 2021-04-09T07:16:43.309600Z

eg. (doseq [x who] (println x))

Rolfe Power 2021-04-09T07:17:23.309700Z

(defn hello [greeting & who]
  (println greeting (first who)))

popeye 2021-04-09T07:17:46.310100Z

ohh got it thanks

practicalli-john 2021-04-09T08:02:28.310400Z

@popeyepwr to return a nicely formatted string from a variadic argument, something like this

(defn hello [greeting & who]
  (str greeting ", " (clojure.string/join ", " who)))

(hello "Welcome" "fred" "betty" "wilma")

hindol 2021-04-09T08:22:33.313200Z

I am pretty sure clojure.string/join and str will individually use Java's StringBuilder internally, but we still end up creating an intermediate string via (clojure.string/join ", " who). I wonder if there is one efficient string joining library out there. Something like,

(make-string **somehow represent the complex join logic here**)
(Maybe one should setup everything with interpose et al. first and then call clojure.string/join only once in the end.)

agile_geek 2021-04-09T08:45:53.313500Z

Just a thought but a lot of financial institutions represent currency values using long values by removing the decimal place and using the smallest currency unit i.e. cents instead of dollars or pence instead of pounds.

1👍
blak3mill3r 2021-04-09T09:40:07.314Z

@hindol.adhya take a look at https://github.com/cgrand/xforms#usage x/str and x/str!

hindol 2021-04-09T09:57:59.314400Z

This is excellent! Thanks.

hindol 2021-04-09T10:05:13.314600Z

But this is a different kind of optimisation. Not exactly what I described above.

2021-04-09T10:54:01.314800Z

something like this? https://github.com/DotFox/matchete/blob/main/test/dotfox/matchete/ref_test.cljc#L33-L61

1👍
CĂ©lio 2021-04-09T12:13:39.315200Z

Something like that. Thanks!

2021-04-09T12:37:59.315400Z

This library is still under develop but feel free to have a look at implementation

Audrius 2021-04-09T12:48:29.317Z

hi! one question about semantics

(merge {:a 1 :b 2 :c 3}{:a 5 :b 6 :c 7})
=> {:a 5, :b 6, :c 7}
Will it always override first arg's fields with second's ?

tvirolai 2021-04-09T12:53:28.317800Z

Yes. For other kinds of merging, merge-with can be used

1✔1🎉
grazfather 2021-04-09T13:38:28.318Z

I guess I’ll take a bit of both. Thank you both.

CĂ©lio 2021-04-09T13:38:41.318200Z

@delaguardo Very interesting. I’m trying to understand how the registry works (the first argument passed to m/matcher).

2021-04-09T13:41:54.318400Z

https://github.com/DotFox/matchete/blob/main/src/dotfox/matchete/compiler.cljc#L287-L292 first argument is a map of identifier -> pattern which you can use later in the pattern given as a second argument for m/matcher wrapped as a [:ref id] sorry for such bloated explanation ) I’m still working on proper documentation for the lib

2021-04-09T13:44:11.318700Z

default-registry function places ::custom (atom {}) as a container for every custom patterns you might want to bring in as a first argument for m/matcher. And during compilation every pattern like [:ref id] will use this atom to look up a matcher to use

zane 2021-04-09T20:07:11.320400Z

How idiomatic would it be to accept required arguments via keywords a-la https://clojure.org/news/2021/03/18/apis-serving-people-and-programs?

alexmiller 2021-04-09T20:13:44.320700Z

depends who you ask :)

zane 2021-04-09T20:14:52.321100Z

I was worried that that might be the response. 😅

zane 2021-04-09T20:15:34.321500Z

And if I’m asking you in particular? 🙂

alexmiller 2021-04-09T20:16:30.322300Z

even I would say it depends on the situation but generally it's ok by me :)

2✔
borkdude 2021-04-09T20:21:44.324700Z

A thought (inspired by Maybe Not I think): what is required now may become optional in the future... and having the required argument positional would make that more difficult (well, you can always pass nil). But making a required argument positional does communicate the intent of required-ness more perhaps.

1➕
zane 2021-04-09T20:42:48.325400Z

> But making a required argument positional does communicate the intent of required-ness more perhaps. Yep! That was why I was hesitating.

sova-soars-the-sora 2021-04-09T22:47:18.327200Z

I want to parse an xml file...

(let [input-xml  (slurp "JMdict_e")]
	(println (parse input-xml))
I'm getting a
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by clojure.lang.InjectedInvoker/0x0000000800b8c840 (/.m2/repository/org/clojure/clojure/1.10.0/clojure-1.10.0.jar) to method com.sun.xml.internal.stream.XMLInputFactoryImpl.createXMLStreamReader(java.io.Reader)
So how can I load a file from the project directory ? o.O

sova-soars-the-sora 2021-04-09T22:50:04.327500Z

using clojure.data.xml

seancorfield 2021-04-09T22:53:00.328400Z

@sova That sounds like you’re using clojure.xml, not clojure.data.xml. The former is “built in” to Clojure itself and is deprecated. The latter is org.clojure/data.xml.

sova-soars-the-sora 2021-04-09T22:54:07.329200Z

I might use into-edn instead https://github.com/thebusby/into-edn

seancorfield 2021-04-09T22:54:11.329500Z

(they both have a parse function so a typo in the :require would easily get you the old/bad version instead of the new one)

seancorfield 2021-04-09T22:54:36.329900Z

org.clojure/data.xml is solid.

sova-soars-the-sora 2021-04-09T22:57:02.330100Z

[clojure.data.xml :refer :all] ought work?

sova-soars-the-sora 2021-04-09T23:01:38.330600Z

Caused by: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,1]
Message: JAXP00010001: The parser has encountered more than "64000" entity expansions in this document; this is the limit imposed by the JDK.
xD

sova-soars-the-sora 2021-04-09T23:03:58.331200Z

I switched slurp to <http://java.io|java.io>.FileReader. and it is working ... 🙂