Fork me on GitHub
#clojure
<
2017-08-01
>
bcbradley01:08:19

hey I was thinking about a way to minimize the memory requirements of a nested clojure data structure where some substructures are identical, and I came up with this:

(partial clojure.walk/postwalk (memoize identity))
but I haven't really given it much thought. How would you do something like this?

noisesmith01:08:36

@bcbradley if you just use the same object as an arg to assoc, it won't be duplicated

noisesmith01:08:02

depending on how the data was created, of course

bcbradley01:08:13

my use case involves taking an .edn file from disk that is probably too self similar and large to fit in memory

noisesmith01:08:25

oh, yeah, fun

noisesmith01:08:29

it would be interesting to try the postwalk identity and then compare the object pointers via jdb maybe(?)

bcbradley01:08:52

yeah i haven't tested it i was just wondering if anyone else had ideas

leira02:08:37

what's "fn*"? I cannot find the document of it~

noisesmith02:08:06

it's an implementation detail of fn

noisesmith02:08:23

fn is implemented as a macro, and uses the destructuring functions that clojure.core defines for macros

leira02:08:28

so means: I don't have to care about it?

noisesmith02:08:30

fn* is implemented in java code

noisesmith02:08:45

right, remembering that it's an fn that can't destructure is probably enough

leira02:08:35

could you please give an example? of the difference?

noisesmith02:08:27

+user=> ((fn [[a]] a) [1])
1
+user=> ((fn* [[a]] a) [1])
CompilerException java.lang.IllegalArgumentException: fn params must be Symbols, compiling:(NO_SOURCE_PATH:2:2)

noisesmith02:08:02

[a] as a parameter is a destructure that says "bind the first element of this sequencable input to the name a"

noisesmith02:08:08

fn* doesn't understand that syntax

leira02:08:06

so the difference is only about the destructuring of the parameters? fn supports it, while fn* doesn't?

noisesmith02:08:24

that's the main one, I forget if it's the only one

leira02:08:50

hmm~ in lazy-seq macro:

leira02:08:57

boot.user=> (source lazy-seq) (defmacro lazy-seq "Takes a body of expressions that returns an ISeq or nil, and yields a Seqable object that will invoke the body only the first time seq is called, and will cache the result and return it on all subsequent seq calls. See also - realized?" {:added "1.0"} [& body] (list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

leira02:08:05

boot.user=> (source lazy-seq)
(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  (list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

leira02:08:30

why is fn* preferred here~? is it some performance concern, as fn* is the basic one?

noisesmith02:08:30

right, destructuring is defined in terms of lazy-seq

noisesmith02:08:37

so lazy-seq can't use fn, which destructures

noisesmith02:08:10

source of fn shows you how it builds the form for fn* - it does many things, some of which are lazy seq generating

leira02:08:34

I will try to read the source of fn I probably digged too deep of the dark magics, as I just started learning Clojure

noisesmith02:08:54

another difference, discovered by reading the source of fn, is that fn* can't do preconditions - or at least not reasonably

+user=> ((fn [a] {:pre [(number? a)]} a) :a)
AssertionError Assert failed: (number? a)  user/eval36/fn--37 (NO_SOURCE_FILE:7)
+user=> ((fn* [a] {:pre [(number? a)]} a) :a)
:a

leira02:08:55

thanks for the explanation, I probably need some more time to digest it~

noisesmith02:08:33

yeah- all you need to remember is fn is fancier, does some things fn* can't, but code that exists before fn is defined (code that fn uses for example...) has to use fn*

leira02:08:38

got it thx!

henrik04:08:04

So I’m parsing XML. That’s enough for wanting to shoot myself in the face, but on top of it I need to check that it conforms to a certain shape, because the source(s) can be unreliable.

henrik04:08:40

I’m using clojure.xml/parse for this.

henrik04:08:10

Now, spec seems like the right tool for conforming. However, I’ve also heard tales of spec being able to destructure as well.

henrik04:08:17

Where can I read about how the destructuring works, specifically? If I can avoid hand-disassembling the stain upon humanity that is the output of parse, I’d be delighted.

henrik05:08:20

I just discovered zippers

delitescere05:08:26

Anyone used clojure transducers for unfolds (anamorphisms)? All talk I’ve seen is on folds (reducers / catamorphisms).

donyorm06:08:31

So I'm working on getting a plugin system working in clojure. Based on the loading system on @yogthos' (https://yogthos.net/posts/2015-01-15-A-Plugin-System-in-Clojure.html). I can get the file on the classpath, but when I try and run the function defined by the .edn file packaged with the plugin, it gives me the error: Could not locate 'oss_world_example/core__init.class or 'oss_world_example/core.clj on classpath.

donyorm06:08:41

I checked in the jar and oss_world_example/core__init.class is definetely there (you can download the example jar I'm using here: https://filebin.ca/3VLfGd71KesW/project.jar). What gives? How is it finding the .edn file but not the classes?

bfabry06:08:45

@donyorm what does the code in those files look like?

donyorm06:08:08

@bfabry oss-world-example.core looks like this:

yonatanel06:08:49

@donyorm You might have an extra quote character somewhere?

bfabry06:08:32

fwiw, I can load that ns from that jar fine

11326-storage:look bfabry$ java -cp ~/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:project.jar clojure.main
Clojure 1.8.0
user=> ( "oss_world_example/core__init.class")
#object[java.net.URL 0x2ca26d77 "jar:file:/private/tmp/look/project.jar!/oss_world_example/core__init.class"]
user=> (require '[oss-world-example.core])
nil
user=> oss-world-example.core/entry
#object[oss_world_example.core$entry 0x24c22fe "oss_world_example.core$entry@24c22fe"]
user=>

bfabry06:08:04

user=> (oss-world-example.core/entry)
Entered the example plugin.
nil

yonatanel06:08:20

Perhaps in the edn file.

bfabry06:08:22

so my bet is you don't actually have the jar on the cp

bfabry06:08:15

the edn file is using underscores for the namespace, which is incorrect

bfabry06:08:27

I'd have to see your loader code to know whether you compensate for that

bfabry06:08:11

but, while that's a problem, it's not a problem that should yield the error you got. the error you got very specifically says the file does not exist in the cp

bfabry06:08:02

(it could be finding the edn file because it's accessible by some other cp entry like .)

yonatanel06:08:34

I think the names in could not locate error message should not start with a quote, so check for single quote in the edn file such as:

{:description "Markdown parser"
 :init 'cryogen-markdown.core/init}

bfabry06:08:51

^ that's it

bfabry06:08:58

I missed the quote

bfabry06:08:42

you don't need to quote inside an edn file

bfabry06:08:46

(edn/read-string "{:foo bar}")
=> {:foo bar}
(class (first (vals *1)))
=> clojure.lang.Symbol

bfabry06:08:12

and in fact it's not part of edn I don't think (' etc are part of the clojure reader)

bfabry06:08:03

yeah

(edn/read-string "{:foo 'bar}")
=> {:foo 'bar}

yonatanel06:08:27

It's surprising in my opinion actually. Maybe it's a bug in the lib, not wrapping with (name symbol) where it should.

yonatanel06:08:06

Nope, I'm wrong

bfabry06:08:09

it's surprising but I dunno about bug... the language reader and the data format reader can't be exactly the same

donyorm07:08:36

That's probably it. I'll check when I get back to my computer.

donyorm07:08:30

Yep removing the quote fixed it. Thanks for your help!

donyorm10:08:38

So I have some code in my -main function (extend-classpath (filter #(.contains (.getName %) ".jar") (file-seq plugin-dir))). If I run this directly in the repl, it changes my classpath as I want it to. If I run the -main function in the repl, it doesn't work. What causes this difference (boot repl if it matters)?

jaymartin13:08:58

@donyorm regarding extend-class in repl vs -main, I’m wondering if it could be related to boot using pods to isolate classpath (https://github.com/boot-clj/boot/wiki/Boot-for-Leiningen-Users#repl-dependencies). Just getting started with boot myself. Would appreciate an authoritative answer too.

mrg13:08:28

There seems to be an issue with 1.9.0-alpha17 in let blocks with auto-gensymed variables in macros. Is that a known issue?

moxaj13:08:25

@mrg could you elaborate?

mrg13:08:05

@moxaj I'm trying to find the example that exploded yesterday 🙂

mrg14:08:57

@moxaj looking again with fresh eyes, I found the bug. I had this:

mrg14:08:25

that fails with something completely unreadable:

mrg14:08:09

trying it in 1.8 I am getting this instead:

moxaj14:08:40

that error message seems crystal clear to me /s

mrg14:08:41

so if I do the right thing and use msg# instead of msg it works

mrg14:08:20

So yeah. Not a bug, just a mistake on my part, coupled with an awful error message 😕

misha14:08:49

what is the general approach to "expanding iteration"?

(path-to-paths
  [:a]
  {:a #{:b :c}
   :b #{:d :e}})
  
;=> [[:a :c]
;    [:a :b :d]
;    [:a :b :e]]
Is this a loop/recur client? Or are there any alternatives? If this is a loop/recur – is there any intuition for how to know how many things I need to keep track of in loop bindings? I seem to spend much more time on these than it should take me.

mrg14:08:13

and got this:

mrg14:08:27

that's pretty neat...

moxaj14:08:04

that's indeed pretty cool!

sjol16:08:19

is there any recommended way of fixing

java.lang.ClassNotFoundException: clojure.tools.logging.impl.LoggerFactory
? This is happening on a fresh project created with lean new luminus <app name> (running [org.clojure/clojure "1.8.0"][org.clojure/tools.logging "0.4.0"] )

donyorm16:08:43

@sjagoe you can consider asking in the #luminus channel. I'll see what I can find

sjol16:08:12

ok, will do!

cjsauer16:08:36

@sjol I seem to remember a similar issue in one of my projects, and the solution was to AOT compile the logging implementation by adding this to my lein project.clj:

:aot [clojure.tools.logging.impl]

theikkila17:08:04

If somebody is ever interested in using ULID:s I ported library for Clojure https://github.com/theikkila/clj-ulid

Garrett Hopper17:08:51

How can I check if something is not equal to one of a few things.

Garrett Hopper17:08:26

Like (not (= % 1) (= % 2))

bfabry17:08:55

(not (#{a few things} thing))

Garrett Hopper17:08:05

Ah, sets, I knew there would be a simple thing I wasn't thinking of.

noisesmith17:08:34

you can use contains? with the set if you need to check for nil / false

noisesmith17:08:43

(along with other non-nill non-false values)

michaellindon17:08:11

is there an elegant way to convert a vector of maps (where each map has the same nested key structure) to a map of vectors? i.e. if i had [{:A {:B 1} :C 2} {:A {:B 3} :C 4}] I would like to get {:A {:B [1 3]} :C [2 4]}

michaellindon17:08:32

@cjsauer thats a good suggestion, let me check it out

bfabry17:08:39

recursive merge-with

bfabry17:08:19

I actually think I've written that before

sjol17:08:40

@cjsauer thank you, I will try that, i thought that ':aot :all' would have taken care of that?

cjsauer17:08:10

@sjol where did you place it? If you put it in your uberjar profile, then it will only take place when you run lein uberjar

michaellindon17:08:50

@bfabry if you could find it then that would be great!

bfabry17:08:49

it's not quite what you want, but it should give you an idea. also it looks like it was written kinda stupidly

(defn merge-metrics
  "Merges a collection of maps together assuming they all have the same layout by recursing
  through any child maps until a non-map is found.
  If the values are a collections, they are appended together.
  Anything other type of value is added."
  [first & rest]
  (if (map? first)
    (apply merge-with (into [merge-metrics first] rest))
    (if (coll? first)
      (into first (flatten rest))
      (+ first (apply + rest)))))

bfabry17:08:35

particularly the recursive call could just be (apply merge-with merge-metrics first rest)

michaellindon17:08:21

thanks for sharing, this looks like a good start for me

sjol18:08:17

@cjsauer yes ':aot :all' was already part of the project.clj file in the uberjar section, when I removed it last night lein uberjar would not work

cjsauer18:08:30

@michaellindon slightly smaller:

(defn merge*
  [res lat]
  (if (map? res)
    (merge-with merge* res lat)
    (into [] [res lat])))
;; now use it
(apply merge* my-maps)

cjsauer18:08:25

@sjol you could try placing the :aot [clojure.tools.logging.impl] at the top-level of your project.clj file; not down in a profile. That way it applies to all lein tasks.

michaellindon18:08:30

@cjsauer ah this is excallent, what a timesaver!

> my-maps
[{:A 1, :B [1 2], :D {:F 10}} {:A 2, :B [3 4], :D {:F 20}}]
> (apply merge* my-maps)
{:A [1 2], :B [[1 2] [3 4]], :D {:F [10 20]}}

michaellindon18:08:29

thanks, i love lisp

sjol18:08:18

@cjsauer will i need to provide a :main as my entry point?

cjsauer19:08:13

I'm not sure whether that will be required. Suppose you could just test it with and without.

markbastian19:08:46

Question for everyone.... if you want to dynamically define a set of ns level functions, what's the right way? I've always been of the opinion that a macro is right, but a co-worker argues that calling a no-arg function in the ns that calls defn is simpler to understand (which may be true). I've always understood the latter idea to be bad, but haven't seen a really solid explanations as to why. Can anyone enlighten me on this? Thanks!

markbastian19:08:00

As an example: Suppose you have a few dbs with names like "foo" and "bar". You might want to dynamically create functions like find-foo, find-bar, write-foo, write-bar rather than use the underlying (db/find "foo" ....) library code every time.

noisesmith19:08:14

with a macro you can ensure that the name being defined is right there in the call

noisesmith19:08:23

with the function, it would be a convention at best, right?

noisesmith19:08:53

also, it’s better to simply not create things like db connections at the global level

markbastian19:08:51

well, suppose the first line of the ns were (defn foo[] (do (defn find-foo.....))) then (foo) right after. The function would exist before you used it.

markbastian19:08:23

I'm not advocating this, just trying to see why it's much worse than (defstuff "foo") where defstuff is the macro that creates the functions relevant to foo.

noisesmith19:08:29

that leads to misbehaving code - clojure doesn’t have a compile-only mode, you can’t syntax check or create a jar from that code without creating a db connection

noisesmith19:08:44

(well you can but it’s complex)

markbastian19:08:33

Excellent points.

noisesmith19:08:55

this is why libraries like stuartsierra/component or integrant exist

noisesmith19:08:17

anyway, def-* is better than defn inside defn

markbastian19:08:15

The db example is just one. the main question is: In any case where you'd want to dynamically generate some functions, what are the reasons why a macro is better than blind function call.

markbastian19:08:36

Unless the answer is "never do that"

markbastian19:08:15

but it seems like something that would occasionally be useful

noisesmith19:08:56

if you are dynamically generating functions, they shouldn’t have to exist at namespace level - otherwise you end up needing to override some of the few protections clojure provides at compile time to create code that uses them

markbastian19:08:25

so, instead letfn or something along those lines as needed?

noisesmith19:08:33

(let [foo-fn (resolve 'foo)] (comment "foo eventually exists") (foo-fn))

noisesmith19:08:42

that’s what you end up with

dpsutton19:08:59

where can i read the source of how keywords act as functions? ie, (:a {:a 1}) Looking at keyword in source, its just interned. But I thought i keywords being proper functions and not just being treated like a function call

noisesmith19:08:17

@dpsutton all things in call position are treated like a function call - this is where it is defined https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Keyword.java#L137

noisesmith19:08:14

they are “proper functions” in that they implement IFn

dpsutton19:08:24

yeah. where is the implementation of IFn

noisesmith19:08:04

s what I linked to

michaellindon19:08:27

@cjsauer , there is one minor issue, the vectors become nested when applied to a vector of maps greater than 2

l
> my-maps
[{:A 15, :B [14 24], :D {:F 40}} {:A 1, :B [1 2], :D {:F 10}} {:A 2, :B [3 4], :D {:F 20}}]
> (apply merge-with merge* my-maps)
{:A [[15 1] 2], :B [[[14 24] [1 2]] [3 4]], :D {:F [[40 10] 20]}}
flatten is not what we need here because ideally for :B we want [[14 24] [1 2] [3 4]]. I'm not sure how to change (into [] [res lat])

michaellindon20:08:01

@cjsauer I tried the following, I don't know if theres a more elegant solution. I needed to create some extra functions

(defn -first [x]
  (if (sequential? x) (first x) nil))

(defn furl [x y]
  (if (= (type y) (type (-first x))) (conj x y) (vector x y)))

(defn merge*
  [res lat]
  (if (map? res)
    (merge-with merge* res lat)
    (furl res lat)))
This now gives
(apply merge-with merge* my-maps)
{:A [15 1 2], :B [[14 24] [1 2] [3 4]], :D {:F [40 10 20]}}

cjsauer20:08:30

Maybe have a look at concat?

cjsauer20:08:47

If you use it with apply it's similar to flattening only one "level"

cjsauer20:08:21

If your code is giving you proper results, I wouldn't worry about "elegance" too much though 😉

michaellindon21:08:24

the trouble i ran into with concat is that (concat 15 1) produces an error message, so if the value for :A in the vector is a 15 and 1, it doesn't give me [15 1]. After the initial construction it would be fine because id have (concat [15 1] 2)

michaellindon21:08:12

seems like the pattern for a reduction operator, where i give an initial value where ive already converted the value collections to vector of collections

dpsutton19:08:29

ah ok. just wondering why ("bob" {"bob" "value"}) wouldn't work. seems consistent with "all things in call position are treated like a function call"

dpsutton19:08:48

so keywords implement ILookup?

noisesmith19:08:52

@dpsutton because string doesn’t implement IFn so you get a “String isn’t an IFn” error

noisesmith19:08:00

it’s the source I linked to

noisesmith19:08:10

they implement invoke, as all IFn must

dpsutton19:08:12

oh man. i missed the filename i was in

dpsutton19:08:14

totally my bad

dpsutton19:08:30

yeah i'm with you

noisesmith19:08:01

it’s right here in the “weird error message” - very clear if you know what you are looking for

=> ("hi" {"hi" 1})
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn  peregrine.circle/eval52026 (form-init7640008060394334379.clj:1)

noisesmith19:08:23

:foo can be cast to IFn, so it works

dpsutton19:08:39

thanks a bunch

michaellindon20:08:34

is there a function s.t. (foo [1 2] [3 4]) gives [[1 2] [3 4]] but also (foo [[1 2] [3 4]] [5 6]) gives [[1 2] [3 4] [5 6]]

ghadi20:08:47

@michaellindon you can write one if you can articulate what makes the first scenario different than the second

michaellindon20:08:11

@ghadi I'm not sure how to differentiate between a vector and a vector of vectors

ghadi20:08:29

how about (vector? (first coll)) ?

michaellindon20:08:09

thats a good point

michaellindon20:08:25

i think i can work with that

ghadi20:08:44

it's just a conj away after that

michaellindon20:08:57

sorry, its been a long day..

misha20:08:34

what is the good lib for working with amazon aws (whatever that might mean)? asking for a friend™

bja20:08:05

depends on what you're doing with it

misha20:08:03

no idea. "asking for a friend" was not really a joke.

misha20:08:30

are there any noticeable alternatives? or it is pretty much it? @bja

bja20:08:39

nothing as comprehensive. there are specific wrappers to S3, SQS, SNS, etc but nothing else (that I'm aware of) besides Amazonica that wraps the entire SDK. I personally tend to use specific libraries for S3/SNS/SQS and then use direct interop in the rare case I need something else. Interop with the Java SDK is pleasant enough for a java library.

misha21:08:29

understood, thank you.

bja21:08:25

Amazonica goes a decent way towards a "native" feel for using AWS via Clojure. If you're just looking for access to the APIs (particularly if you have experience with them), importing the Java SDK directly is an option. The rationale behind not using Amazonica would be you want to avoid its extensive reflection usage (although how much that actually matters as a remote api wrapper is questionable)

noisesmith21:08:15

or because you decided to look for the source to that one function you use and discovered the whole thing is a bed of lies

noisesmith21:08:57

the structure of the project is pretty startling though - all the definitions are created by macros that reflect on the objects in the aws jar to decide what to create

noisesmith21:08:29

it’s only a concession to sanity that they give you namespace files - they could have faked that up too

noisesmith21:08:49

… or maybe not, it would make it hard for require to work as expected now that I think of it

bja21:08:09

that's part of the extensive reflection that I wasn't happy with

bja21:08:37

my original rationale for not using it was that it didn't let me directly select which client object I was using, but I found out that I just didn't understand what it was doing

bja21:08:57

by that point, I had already built on top of clj-aws-s3 and bandalore though

ghadi21:08:57

Not related to AWS libs, but the recent release of clojure.tools.deps directly interops with Maven Resolver's java API. It's a really nice reminder of how clean and easy interop is.

ghadi21:08:26

back in the early days there were a million redis wrapper libraries, but these days I just use Jedis directly.

misha21:08:06

@ghadi still no videos from euro-clojure? harold

ghadi21:08:16

i'm sure they'll be coming soon!

ghadi21:08:32

in the meantime the Java Language Summit is releasing videos, always interesting

ghadi21:08:46

Unfortunately no Clojure representation at the summit this year

ghadi21:08:40

though Brian Goetz had some very flattering things to say about clojure.spec to a large Java audience (at some other conference)