Fork me on GitHub
#clojure
<
2019-03-28
>
vemv07:03:14

Trying an 'official' reducers example, but adding a println:

(->> (range 100000)
         (r/map (fn [x]
                  (println (Thread/currentThread))
                  x))
         (r/filter even?)
         r/foldcat)
...how come it always prints the same thread? I'd expect things to use all available cores

dominicm07:03:35

Range is reducible though :thinking_face:

👍 4
vemv09:03:58

how do I get reducers to process each member of the coll in a separate thread? I tried with (fold 1 ...) but no luck. The forkjoin pool is used, but each member is processed in the same thread, which proves sequentiality (I might be better off with pmap. But reducers might give some flexibility)

vemv09:03:54

Maybe this is my answer: > . Similarly, reducers is elegant, but we can't control its level of parallelism. https://github.com/TheClimateCorporation/claypoole

jsa-aerial14:03:12

The key to this is to get the 'right' chunk size for fork/join packets. If it is too small you will just context switch. Too large (for your collection size) won't make enough packets to cover the available cores. Have a look at this https://github.com/jsa-aerial/aerial.utils/blob/d451f81fc308a7fbae2ca1de22890c2e4f726880/src/aerial/utils/coll.clj#L468, it has worked very well for me.

vemv14:03:09

Super interesting! Will study it

tomaas10:03:53

Hi, Im new to docker and have to add a clojurescript project to an existing Dockerfile. It uses the ubuntu:latest. Adding leiningen to apt-get install fails. Does anyone have a basic configuration to show how to do this in ubuntu image?

victorb11:03:12

based on the official clojure image rather than ubuntu though

victorb11:03:41

if you're planning to use in production, you probably want to lock down the dependency versions, as they are not in the example linked above

tomaas14:03:21

i needed to use the ubuntu image, finally i've solved that putting this

hipster coder15:03:47

I like your way better... but someone made a ppa...

hipster coder15:03:50

sudo apt-add-repository ppa:/mikegedelman/leiningen-git-stable sudo apt-get update sudo apt-get install leiningen

hipster coder15:03:25

It just tracks the stable lein repo on github

hipster coder15:03:43

@UEJ5FMR6K is 100% correct. Lock the clojure version, lein version and dependencies versions... or you could be in dependency hell if just 1 library version changes

borkdude10:03:59

I need something like this: http://jsondb.io/ with a focus on performance. But with the ability to query JSON/EDN without parsing the whole database up front (performance). And the ability to write objects very fast, without re-indexing the whole database. Right now I’m doing the naive thing: read a giant EDN file, query it and write a giant EDN file.

borkdude10:03:44

One way of optimizing this is to split the EDN file into multiple partitions and only read/write the necessary partitions, but there might already be a tool which does this. Another requirement: it must run with GraalVM. I already tried nippy/codox which currently doesn’t run with that.

vlaaad11:03:59

smartest function composition I ever wrote:

(defn- wrap-dedupe-args [f]
  (let [dedupe-fn ((dedupe) (fn [_ args] (apply f args)))]
    (fn [& args]
      (dedupe-fn nil args))))

(let [f (wrap-dedupe-args prn)]
  (f 2)
  (f 2)
  (f 2)
  (f 3)
  (f 3))
2
3
=> nil

😮 8
🤯 8
4
hipster coder15:03:35

It is formatted very nicely... but I don't understand what it does

hipster coder15:03:02

What does prn mean?

vlaaad15:03:16

@UGQJZ4QNR prn is similar to println which prints it's arguments, and the difference is prn prints objects so they can be read back. In practice it usually means that prn prints some additional data that helps to understand what exactly is being printed. For example, (prn false "false") will print false "false", making it easy to understand what exactly these printed values are (compare to (println false "false") that will print false false).

hipster coder15:03:00

ahh, it makes it more readable

vlaaad11:03:23

[ab]using transducers to deduplicate side-effecting calls by their args

beoliver12:03:26

Does anyone know of a library that normalises local variables? clojure.tools.analyzer.passes.jvm.emit-form/emit-hygienic-form seems to be the closet, but just appends a __#<int> to the var labels. I would like to rewrite variables into the form x__#<int> where x is an arbitrary constant. This would mean that (defn f [a b] (let [a (+ a b)] a)) would become (def f (fn* [x__#0 x__#1] (let* [x__#2 (+ x__#0 x__#1)] x__#2)))

dpsutton12:03:02

you could walk the ast and rename them probably? take inspiration from tools.analyzer.passes.uniquify

beoliver12:03:05

yeah, that’s exactly what I have been doing, was having some problems with dynamic bindings so I just thought I would see if someone had already written this…

hipster coder15:03:22

Is dynamic binding the same as calling a function by variable name?

beoliver17:03:58

exactly - or just a value ie using binding form

hipster coder18:03:56

How does it know what nth is?

hipster coder20:03:06

It is basically a more robust way to encapsulate lexical scope of vars

hipster coder20:03:28

And then return control back to the original scope

beoliver22:03:53

binding is used when you want to redefine the value of a variable within a code block. The variable needs to have been the ^:dynamic meta - so lets say you have a variable that is defined in some other namespace. I think this example should make sense:

analysis.core> (def ^:dynamic hello "hello")
#'analysis.core/hello
analysis.core> (defn print-hello []
                 (println hello))
#'analysis.core/print-hello
analysis.core> 
analysis.analysis> (binding [analysis.core/hello "bye"]
                     (analysis.core/print-hello))
bye
nil
analysis.analysis> 

beoliver22:03:45

It is thread local - the macro expansion makes this clearer:

analysis.analysis> (macroexpand '(binding [analysis.core/hello "bye"]
                                   (analysis.core/print-hello)))
(let*
 []
 (clojure.core/push-thread-bindings
  (clojure.core/hash-map #'analysis.core/hello "bye"))
 (try (analysis.core/print-hello) (finally (clojure.core/pop-thread-bindings))))

hipster coder15:03:15

In ruby we use { } for a code block. It has a local scope but also has access to outside scope (closure). But I don't think Ruby can access different namespace scopes like Clojure bind appears to be doing.

hipster coder15:03:30

So instead of passing parameters to a function, you just bind new variable values to the namespace before running code block or functions. It's almost like a global var but probably immutable?

g15:03:12

hey everyone, i was wondering if there a consensus on the best pattern for a fn defn that can: either perform an operation on one thing or an operation on a collection of those things

g15:03:36

i guess the front runner in my mind is defn operation [ & args ] and then either (operation thing) or (apply operation things)

vemv15:03:30

Worth pointing out, apply isn't efficient

g15:03:40

oh yeah? how so?

g15:03:52

i’m not super concerned with that at the moment, but it’d be good to know in the periphery

vemv15:03:16

IIRC it boils down to the fact that you are creating short-lived lists in runtime, adding pressure to the GC

g15:03:32

hmm, cool

Alex Miller (Clojure team)15:03:23

my advice would be not to do that

Alex Miller (Clojure team)15:03:38

in that, every time I've ever done that, I regretted it later

Alex Miller (Clojure team)15:03:26

either pick "one thing" and use it with seq ops like map/filter etc or make it take a coll

g15:03:01

interesting. that’s what i have now, but it feels kind of gross

g15:03:12

could be lack of experience, though

g15:03:25

thanks!

hipster coder15:03:48

Does '''apply''' operate 1 by 1, on each thing?

hipster coder15:03:14

Or just apply the new list of arguments to the function?

hipster coder15:03:57

Reading the docs... it looks like it applies new args to the function

dpsutton15:03:28

Hard to parse what you are getting at

hipster coder15:03:14

Function overloading? Argument overloading

hipster coder15:03:34

(apply + 1 2 3)

chrisulloa15:03:15

that wouldn't work

chrisulloa15:03:25

you need to do (apply + [1 2 3])

chrisulloa15:03:36

which is the same thing as (+ 1 2 3)

hipster coder15:03:51

ahhhh ok, so it does apply the arguments as a whole

hipster coder15:03:57

I was thinking that apply operated on each argument, 1 at a time

hipster coder15:03:10

e.g. apply the function to each argument

g15:03:36

sorry i think i made a mistake

chrisulloa15:03:36

yeah you will also see sometimes (reduce + [1 2 3]) which is something like (+ (+ 1 2) 3)

g15:03:56

generally i’m looking for the optimal way to write one or more functions that do the same thing but either operate on one item or several

g15:03:12

[idiomatic|optimal]

g15:03:30

sounds like two functions has worked well

chrisulloa15:03:00

you can use multi arity functions for that

hipster coder15:03:05

you need a map

g15:03:39

i don’t think multi arity functions would work; i should have specified the ‘several’ is a collection

hipster coder15:03:54

map is used to work on collections

lilactown15:03:14

@gtzogana I think the idiomatic thing to do is to define a function that operates on one, and then compose that with the various seq operations like map reduce transduce etc.

g15:03:40

right, i guess normally that would be the answer. i guess my question is too abstract. i’m specifically looking at database inserts.

chrisulloa15:03:43

gotcha not sure what you're trying to do maybe i missed an earlier convo

g15:03:13

nope, i deliberately asked the question a little too abstractly. my bad

lilactown15:03:37

so you have a fn like insert-item! that you might call like (insert-item! item1) or (insert-item! [item1 item2])?

g15:03:38

so, in particular, either way i’d be calling jdbc/insert-multi!

hipster coder15:03:57

personally, I thought you were just trying to apply a new set of arguments to the same function… method overloading

hipster coder15:03:10

but you mean a collection

g15:03:11

yeah, not quite

g15:03:47

in the case of the single item, i’d be looking at insert multi! [item], in the other insert-multi! items

g15:03:54

and was trying to avoid seq? item

lilactown15:03:11

personally I think that “overloading” your function signature like that is usually a bad idea

lilactown15:03:24

and I would consider it un-idiomatic

hipster coder15:03:35

I agree… be careful with overloading, especially multi arity

hipster coder15:03:58

in my opinion, multi arity is really object orienated style

g15:03:07

“overloading,” meaning have it accept either a collection or a single item

g15:03:24

ok, makes sense. sounds like that is the consensus

lilactown15:03:45

I would prefer it to always operate on colls. I agree with Alex, I’ve done stuff like that before and usually come to regret it 😬

g15:03:11

yeah that’s interesting. could you elaborate a bit on how, if any details come to mind?

hipster coder15:03:23

could you just put the single item into a collection?

hipster coder15:03:31

so that the function always takes the same type

g15:03:35

i could, yes. just call it as do-thing [item]

g15:03:41

but i’d rather not have to remember to do that

g15:03:55

or, rather force others to remember to do that

hipster coder15:03:30

could just convert it, inside the function? no?

g15:03:02

yup. but i would also like to avoid seq? item. which, again, is kind of a personal aesthetic preference, i guess.

Alex Miller (Clojure team)15:03:12

run! is like map, but for applying the same side-effecting function

hipster coder15:03:15

ahhh ,gotcha, avoid type checking

👍 4
chrisulloa15:03:00

are you trying to do a bulk insert operation?

g15:03:04

@alexmiller, that’s cool, didn’t know that. i would have reached for doseq.

chrisulloa15:03:06

instead of inserting 1 by 1

g15:03:12

so, anything map-like is not particularly desirable

chrisulloa15:03:52

if it's a bulk insert i think it should only work on a collection

👍 4
chrisulloa15:03:21

well that's the assumption i would make if i saw something called insert-multi

hipster coder16:03:09

what about looking for a batch insert? are you just trying to make the map for efficient?

hipster coder16:03:23

in ruby, we would batch insert with map

hipster coder16:03:31

since ruby is very slow

g16:03:38

there’s definitely a batch insert. my concern was more around how to wrap it.

g16:03:46

i’ll just go with two functions

hipster coder16:03:12

there is partition if you want to break the collection into smaller chunks

👍 4
g16:03:23

thanks!

hipster coder16:03:48

just in case you have millions of items in the collection, and don’t want to suck up all your JVM’s memory… since JVM can use a ton of memory

hipster coder16:03:51

can I ask a question though? why would a type check be bad in this case?

g16:03:17

i can’t say i have a super rigorous backing for that preference

g16:03:01

one thing is i just find that, if i can completely avoid it, it reduces the number of code paths i could be looking at

g16:03:51

which is definitely an argument for more than one function

hipster coder16:03:58

ya, the type check is also coupling… still thinking

hipster coder16:03:40

my thought, comparing this to OOP, is to use dependency injection

hipster coder16:03:01

but only use that pattern if you find that you need lots of duplicate functions

hipster coder16:03:21

build yourself a to_collection function, that can turn any other single data into collection type

hipster coder16:03:28

but that’s only going to be good, if it’s readable, and 1 to_collection function can save you from writing dozens of duplicate code

hipster coder16:03:42

so it’s a little premmature to use the pattern

g16:03:05

yeah, also a great idea

g16:03:06

seeing how this one plays out may be a good indication

Alex Miller (Clojure team)16:03:46

in general, Clojure rewards "thinking in collections". you have a huge library of seqable->seqable functions for doing so, threading macros like ->> built to create pipelines, etc

Alex Miller (Clojure team)16:03:38

many functions tend to be a predicate (takes a single element and returns logical result) or a transformation (element -> element)

Alex Miller (Clojure team)16:03:12

generally I find you should mostly do those, and use sequence functions to transform from collections of elements to collections of elements

Alex Miller (Clojure team)16:03:44

when I've created functions that take 1 or N, it's usually because I haven't thought enough about how the function is going to be used. and when I have created it, I have ultimately had it break down because my single element actually turns out to be a collection itself and I can no longer distinguish

Alex Miller (Clojure team)16:03:54

if the function you're writing is "insert-multi", I can't imagine it makes sense to take anything but a coll

dominicm16:03:06

The other place I see it used us as an attempt at aesthetics.

Alex Miller (Clojure team)16:03:29

much better aesthetically to have a single calling convention than 1 or N

👍 4
andy.fingerhut16:03:54

Not being able to later distinguish between 1 vs. N case, because the 1 case is itself a collection, is certainly a bad place to end up.

4
Alex Miller (Clojure team)16:03:22

one place that makes this stuff visible is creating specs

Alex Miller (Clojure team)16:03:48

if writing the spec for the function args is hard, then that's a good sign that you have possibly introduced unnecessary complexity

andy.fingerhut16:03:06

On the comment earlier of "could be called as (do-join [thing]), but would prefer not to remember that", I don't want to sound too forceful, but if the doc of the function says that it always takes a sequence of 'thing', then in Clojure the syntactic overhead of putting thing into a sequence like a vector is very very light, and common in Clojure code.

seancorfield16:03:16

I'll +1 Alex here. Early on in our Clojure development (2011), we wrote quite a few functions that could either take a collection or a single element -- and that code has been an albatross ever since, until we rewrote it to either always take a collection (and pass [item] in calls that had a single item) or to always take a single item (and call map on it for places that wanted a collection processed). In other words, Stuart Sierra's "heisenparameter" recommendation is what you should be doing 🙂 (or rather "don't").

Alex Miller (Clojure team)16:03:04

arguably done in the sake of aesthetics, but it makes me deeply uncomfortable

dtsiedel16:03:56

Is instrument + instrument-multiple bad for aesthetics? It sounds good to me

Alex Miller (Clojure team)16:03:27

arguably multi is more primary here, so I'd probably argue just doing instrument that takes a coll

hipster coder17:03:59

I am new. So it’s better to just convert the single item into a collection type? then pass it to the function. So the function always expects the same type (collection)?

seancorfield17:03:26

In general, yes, but if you have a function that just calls map on its argument, it's probably better to name the function that is being applied, and to have callers use map on that instead.

hipster coder17:03:19

and rely on function doc comments to know what to pass to it?

hipster coder17:03:04

I definately in favor of functions taking only 1 type and returning 1 type

hipster coder17:03:16

Because functions calling other functions... if just 1 function in the chained calls, stack, messes up, you have to trace it, then possibly refactor code all over the place

hipster coder17:03:55

Func1.func2.func3.fun4 oops... func4 wanted a collection, not a string

hipster coder17:03:29

In ruby... they say... just don't use more than 3 chained calls :rolling_on_the_floor_laughing:

hipster coder17:03:35

I started writing the return type into my function names, to make code readable...

hipster coder17:03:59

func_coll.func_str.func_int

Alex Miller (Clojure team)17:03:25

well I can't endorse that :)

😂 4
dpsutton17:03:23

that notion is called Hungarian notation i think.

dpsutton17:03:42

and the only popular vestiges that I know of are in interface names

hipster coder17:03:28

So its ok for Clojure functions to handle multiple types? Please correct me

Alex Miller (Clojure team)17:03:02

it's possible. whether it's "ok" is entirely contextual

Alex Miller (Clojure team)17:03:32

certainly there are many polymorphic functions in the core lib

noisesmith17:03:37

for comparison, OCaml has different math functions for floating point vs. fixed point (calling + on a float is an error, calling +. on an int is an error)

noisesmith17:03:19

clojure is pretty deeply polymorphic, it's one of my favorite things about the language

lilactown17:03:28

probably one of the most annoying things about OCaml IMO

lilactown17:03:44

not specifically the +/+. thing but the general lack of modular implicits

hipster coder17:03:55

OCaml is pretty strict on types?

noisesmith17:03:30

extremely so - ML set the paradigm for modern type-safe languages

hipster coder17:03:08

Everyone keeps praising Haskell strict types.

hipster coder17:03:28

But then people say opposite. That inferred types is bad

hipster coder17:03:50

I have no idea. I do think a lack of polymorphic duct typing is annoying. With the + operator

noisesmith17:03:17

type inference is just a convenience for strict typing - if your system is strict enough the compiler can know which type has to be used

noisesmith17:03:42

OCaml has an object system that is 100% duck typed, but the percentage of people writing OO code in OCaml is small

noisesmith17:03:35

I think interface dispatch / polymorphism is the good part of OO and I'm glad Clojure embraces it while avoiding concrete inheritance and enterprise design patterns

noisesmith17:03:02

they also have different fold (aka reduce) for different sequential collection types - very annoying

dpsutton17:03:03

F# was annoying like that as well

noisesmith17:03:05

yeah, F# was explicitly an attempt to copy OCaml for the CLR

Alex Miller (Clojure team)17:03:21

wasn't it even a strict superset originally?

noisesmith17:03:19

that sounds right but I'm not sure

noisesmith17:03:42

but back to Clojure - polymorphism is normal, and it works well - especially the way of writing code to interfaces / protocols

hipster coder17:03:20

Regarding earlier... I think he just needs an implicit type... e.g. single string converts into collection with 1 string element.

hipster coder17:03:52

Can clojure do implicit conversions? Or is that bad idea?

hipster coder17:03:05

Hmmm. Scala and C++ seem to be against implicit type conversions. Not sure about Clojure.

borkdude17:03:18

it’s basically a lack of type classes what makes this annoying (if I’m not mistaking?)

borkdude17:03:52

implicit type conversions is something different from polymorphism

borkdude17:03:16

Clojure doesn’t convert a string to an integer automatically

andy.fingerhut17:03:02

nor a string into a list of strings, nor vice versa. I certainly wouldn't want it to automatically do so.

hipster coder17:03:08

Ya... (+ "a" "b") gives me a cast error

borkdude17:03:02

I kinda don’t like functions who deal with either 1 thing or a list of things. It begets a lot of checks like: am I dealing with a collection here?

👍 4
Cameron19:03:40

I like to work with a single unit and find a way to arrange that function in a way that can process more volume if necessary. Sometimes just map of course 🙂

beoliver19:03:42

(defn f [])
(def g #())
Is there any reason why calling g returns a clojure.lang.PersistentList$EmptyList where as calling f returns nil?

ghadi19:03:58

I think you mean the opposite @beoliver

ghadi19:03:11

oops you edited 🙂

ghadi19:03:16

user=> (macroexpand '#())
(fn* [] ())

beoliver19:03:00

Was this a conscious design decision?

hiredman19:03:51

# means "a function that has the following form as its body"

hiredman19:03:09

so for #() the following form is () the empty list

hiredman19:03:47

for #(+ 1 2) the following form is (+ 1 2) which is invoking the function + with the args 1 and 2

beoliver19:03:16

but then again () is an invalid form unless it is a macro

beoliver19:03:26

sorry - quoted '()

hiredman19:03:54

user=> ()
()
user=> 

beoliver19:03:58

where did I get that from?

beoliver19:03:07

I was certain 😅

hiredman19:03:07

some other lisps are like that

dpsutton19:03:10

coworker had a question about reduce-kv: is this purely convenience? Seems like a strange function for core (despite us using it a bunch)

ghadi19:03:02

reduce-kv allows consuming a map without boxing the entries [k v]

dpsutton19:03:42

clojure.lang.IPersistentMap
 (kv-reduce 
  [amap f init]
  (reduce (fn [ret [k v]] (f ret k v)) init amap))
is in core. isn't this the naive thing?

joelsanchez19:03:05

sorry for breaking the discussion but Stuart talked about the (if (coll? arg) arg [arg]) antipattern here https://stuartsierra.com/2015/06/10/clojure-donts-heisenparameter

dpsutton19:03:37

thanks @ghadi. gonna read over and mull this.

johnjelinek20:03:43

so, I can start a rebel-readline like this:

clojure -Sdeps '{:deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"}}}' \
  -m rebel-readline.main
and an nrepl like this:
clj -Sdeps '{:deps {nrepl {:mvn/version "0.6.0"}}}' -m nrepl.cmdline --interactive
but, how do I do an nrepl + rebel-readline?

Cameron20:03:05

sorry if not related 🙂

johnjelinek20:03:11

is there an inline way to express it without needing to make changes to deps.edn?

Cameron20:03:24

do you mean like clj -A:1.7:bench:test... where 1.7, bench, and test are three separate aliases you have set up? you just use the -A flag

Cameron21:03:11

that's all I have, might need to ask again if that isn't what you need 🙂

johnjelinek21:03:45

in my case, I have a docker container with clojure/clj binaries in it and no access to the filesystem and can't mound things in ... if I want to string together multiple deps and two main entrypoints (e.g.: rebel-readline and nrepl mains), how would I do that?

Cameron21:03:11

I see what you mean now, you are referring to the command line argument for the entrypoint for each dep. what is your reason for trying to use two entrypoints at the same time?

Cameron21:03:07

Could you specify your own entry point and call into your dependencies' entry points from there?

johnjelinek22:03:24

well, I want the readline capabilities of rebel-readline and I want the nrepl port listening at the same time

johnjelinek22:03:48

so, I guess maybe that's where that fancy -e work would need to come in

Cameron22:03:11

It's probably a subjective question, when and where to start nrepl/rebl - so you may just want to ask again in the main chat 🙂

lilactown20:03:54

there's probably something fancy you can do with -e but it's not going to be fun to type

WhoNeedszZz20:03:02

What is the idiomatic way of generating a list or map from a starting value that has some function applied to it iteratively? This would be the equivalent of an imperative for loop where the end condition is that the initial value has reached or dropped below 0.

WhoNeedszZz20:03:20

For example, a credit card payment calculator

WhoNeedszZz21:03:29

Ok, so I looked over the docs and they aren't very clear in how exactly it works. Specifically I have an issue where the examples use partial, but that seems to only work when the unknown argument is not the first argument. In a simple function where one number is subtracted from the other this won't work because it's the first number that is unknown, not the second.

WhoNeedszZz21:03:38

For example, let's create a simple function for paying off a 0 interest balance so the function would be (- balance payment). It's balance that needs to be replaced; not payment.

ghadi21:03:00

post a full example of what you're looking for

WhoNeedszZz21:03:16

I just gave a specific example

ghadi21:03:34

what is the input, and what is the full sequence that you want to get out?

nwjsmith22:03:47

@whoneedszzz how about:

user=> (let [payment 2] (take-while pos? (iterate #(- % payment) 11)))
(11 9 7 5 3 1)
?

WhoNeedszZz22:03:59

Ended up with something very similar, but instead of pos? I'm using (>= 0 %)

WhoNeedszZz22:03:49

Though it actually makes more sense to use pos? thinking about it more as depending on the last balance and payment amount it may be less than the usual monthly payment

nwjsmith20:03:23

user=> (doc iterate)
-------------------------
clojure.core/iterate
([f x])
  Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects

metametadata21:03:30

> Is there a way to control the name of a reify class? I like to have that for profiling to be able to see which implementation I have in the profiler. @akiel have you found any workaround?

noisesmith21:03:09

I think at that point you can just use deftype right? or does it lack some advantage reify has?

noisesmith21:03:40

(it doesn't capture scope like reify, but you can translate that into field(s))

metametadata21:03:18

I've never thought about it, but longish classnames generated by reify are indeed inconvenient. Yeah, no scope capturing would be a downside. Maybe one could also try overriding toString inside reify, but then it's probably impossible to defmethod print-method for such instance..

noisesmith21:03:58

you could probably defmethod print-method for (class my-reify)

5
noisesmith21:03:25

and yeah, overriding toString does also work iirc

WhoNeedszZz21:03:20

Is there a way to use partial when the first argument is the one you want to replace?

noisesmith21:03:37

user=> (reify java.io.Closeable (close [this]) (toString [this] "my closable"))
#object[user$eval138$reify__139 0x37ddb69a "my closable"]

metametadata21:03:33

yeah, cool! not ideal, but better than the default

noisesmith21:03:50

@whoneedszzz you can define it, the hard part is a good name

dpsutton21:03:47

also (partial #(func % second-arg)) if you don't want to think of a good name

noisesmith21:03:24

fair enough - a cute name for it that I've seen is qartial (since q is a backward p)

🍭 12
dpsutton21:03:04

that's fantastic

noisesmith21:03:06

I can't take credit, and in practice I came back and found it used and forgot what it meant and expected it to be some kind of polynomial function

noisesmith21:03:35

so ¯\(ツ)

4
borkdude21:03:25

What’s the difference between fressian and nippy?

borkdude21:03:44

are they conceptually the same but different implementations, or cover entirely different use cases?

noisesmith22:03:54

it tests and records speed of both

hiredman22:03:15

nippy is like: what if we made bytes out of things instead of printing them and gzipped those bytes

borkdude22:03:24

so fressian is better for reading/writing EDN to disk and nippy may be a bit faster than that?

hiredman22:03:28

fressian is like: lets take a survey of serialization formats, find one that matches what we want for clojure, and include some features like caching to improve performance

borkdude22:03:19

if I have a command line app that needs to read and write EDN data from disk and there is no long-running process, does fressian still make sense?

hiredman22:03:34

I have only used nippy in the context of using libraries that brought nippy along for the ride, and in ever case formed an unfavorable opinion of those libraries, which has rubbed off on nippy

hiredman22:03:00

how much data?

borkdude22:03:40

the max edn file I’ve seen right now is about 2MB. but I’m going to split it into multiple EDN files anyway, so probably smaller

hiredman22:03:57

I would go down a list something like pr > transit > fressian

hiredman22:03:53

but very likely somewhere between pr and transit I would switch to using apache derby

borkdude22:03:39

I’ve tried Derby but that doesn’t compile with GraalVM 😞

Alex Miller (Clojure team)22:03:44

I think Stu's fressian talk from years ago did a pretty good job at highlighting the goals and tradeoffs of fressian (pre-dated transit, which has different tradeoffs)

Alex Miller (Clojure team)22:03:27

plus, that's like peak mullet Stu

💯 4
borkdude22:03:33

haha. thanks

seancorfield22:03:07

Can I just say how much I love tap>? ❤️

seancorfield22:03:16

(and REBL 🙂 )

parrot 8
borkdude22:03:56

I did a quick comparison between pr-str, fressian and transit. Reading: EDN 100ms, Fressian 20ms, Transit 20ms Writing: EDN 74ms, Fressian 50ms, Transit between 1000 and 2000 ms. So it seems that Fressian is the clear winner here, and I don’t know why Transit writing takes that long…

borkdude22:03:18

The map has 799 keys and is about 1.8MB in raw EDN.

ghadi23:03:29

Kinda hard to say without code

borkdude23:03:35

if you want, I can provide you with the data

ghadi23:03:52

Offhand, it is benchmarking file io

hiredman23:03:03

/tmp maybe a fast in memory filesystem, etc

borkdude23:03:20

this is my home folder

borkdude23:03:51

oh hmm, I’ll try to write fressian not to /tmp

ghadi23:03:03

It is also not accounting for JIT warmup

hiredman23:03:43

also, not related to performance, doing defs like that is super gross

hiredman23:03:10

clojure is not scheme, def always creates a global name, no matter where it is nested

borkdude23:03:25

it is, that’s the point of this tool: https://github.com/borkdude/clj-kondo/#rationale 🙂

hiredman23:03:48

don't do that though

borkdude23:03:53

I know that it’s doing that, I wanted to inspect the value after the run, this is throwaway code

metametadata23:03:54

I'd suggest also trying Jsonista

ghadi23:03:26

Anyway, we use transit at work all the time, it's fast.

ghadi23:03:36

Something is wrong with the benchmark

hiredman23:03:50

like, it is entirely possible to build and operate large complex clojure code bases without inline defs to debug things

ghadi23:03:05

I would try to eliminate file IO and account for JIT and then recompare

ghadi23:03:25

My assumption is that transit is faster than edn and slower than fressian

hiredman23:03:31

the idea that you do it so often that you need a tool to make it easier is just, I dunno, what are we doing here

borkdude23:03:46

I don’t make it easier, I just don’t want to commit it if I’ve done it. I find it easy to capture e.g. requests and then inspect them. Works for me. You can also use an atom for this, or scope-capture, I don’t care.

borkdude23:03:19

I’ll re-benchmark tomorrow, it’s getting late here.

5
borkdude23:03:46

I updated the benchmark so I can run it with either edn, fressian or transit from a cold JVM as a cmd line invocation. These are the numbers (code is above it): https://github.com/borkdude/clj-kondo/blob/fressian/src/clj_kondo/main.clj#L59 This is the test file: https://www.dropbox.com/s/qkhcm5x9fkhvw1f/test.edn?dl=0 I’m seeing the same thing with transit.

borkdude23:03:27

I’d be curious to hear if any one of you can reproduce this. I’m afk now. Goodnight.