Fork me on GitHub
#clojure
<
2020-06-04
>
didibus01:06:04

Was wondering, in Clojure, what's the best way to model an ordered list where I need to remove and insert from inside?

didibus01:06:56

Normally, a linked list works really well for this. Since you can add elements in the middle easily, without needing to push out all the elements after or move up the ones before.

didibus01:06:10

But the list in Clojure does not have such an insert/remove

hiredman01:06:09

Hard to say, I might just use a mutable LinkedList

hiredman01:06:01

Algorithms that require that sort of thing tend to be very imperative anyway

lilactown01:06:23

You could probably build a simple insert fn and remove fn for lists. I don’t come across many use cases that really need to operate on indices like that tho

didibus02:06:49

That's most likely what I'll end up doing, and just loop over it to rebuild, since I don't have a big list anyways.

lilactown01:06:36

Another option could be to use a sorted map

lilactown01:06:47

Again, depending on your use case

hiredman01:06:32

Although, even a LinkedList isn't ideal if you are doing random accesses

raspasov01:06:00

@didibus also, alternatively, a sorted map?

raspasov01:06:02

sorted-map already suggested by @lilactown , didn’t see

raspasov01:06:25

I would probably use a sorted-map for something like that; alternatively I guess you can split and re-concat vectors via rrb-vector, but it’s somewhat more involved

raspasov01:06:08

If it’s a small enough of a collection, you can even just (remove …) (filterv …) from a vector?

raspasov01:06:54

(that would be linear time, but depends on the size of your vector might be OK)

didibus01:06:58

Hum.. ya sorted-map wouldn't work, because I don't have anything to sort on. Though... I did think I could have an order key, and if I make it a double, in theory, I could take the element before and after in the current order, and make the new element the middle double value between the two

raspasov01:06:25

@didibus I’ve previously used rrb-vector where I split the vector and re-concat it…

raspasov01:06:58

subvec, catvec

didibus01:06:07

rrb-vector is probably the best I guess. For my use case, I'll probably just go with a vector that I split, conj/pop and concat.

didibus01:06:19

But I got curious

didibus01:06:45

Was just showing someone how to build a quick todo list app, and got a bit choked on the drag and drop for moving items in the list around 😛

didibus01:06:52

Mutable linked list could do as well I guess but :S

raspasov01:06:58

@didibus haha got it… Yea I would say sorted-map can work there, just have a map like {1 {:data 42}, 2 {:data 43}}

raspasov01:06:44

Sort by the key…

raspasov02:06:09

Re-arranging things can get complicated though…

didibus02:06:16

Hum, that's kind of what I had, but if you have: {1 {:data 42}, 2 {:data 43}, 3 {:data 34, 4 {:data 34}} and you want to move :data 34 after :data 42 ?

raspasov02:06:21

rrb-vector it is I think 🙂

didibus02:06:31

You need to kind of iterate over all, increment, and all that

raspasov02:06:08

Yea, I agree, not the best… rrb-vector 💪

didibus02:06:16

Which is where I thought, could make the key a double, so I would just turn: 4 {:data 34} into {1.5 {:data 34} haha

parrot 4
didibus02:06:11

It just started to feel like I was stretching things a little far with that 😛 Thought there has to be something more obvious

raspasov02:06:38

yea… rrb-vector is my vote for sure… more idiomatic; less accidental complexity data around

didibus02:06:42

Which I guess there isn't, this is actually quite a non trivial thing if you want to be immutable and try to approach O(1)

didibus02:06:12

Ya, I heard some rumors that rrb-vectors has a lot of known bugs in its imlementation?

didibus02:06:30

Was that old/fake news ?

raspasov02:06:12

Somebody more familiar would have to chime-in… I am not up-to-speed on it; I’ve used it but it has been years, it wasn’t recently

raspasov02:06:14

It’s moved under clojure / in github… it would hope it’s relatively OK to use

didibus02:06:36

Ya, I'll give it a roll one day

didibus02:06:41

thx all

✌️ 4
clj 4
andy.fingerhut02:06:26

rrb-vector does have at least one remaining bug that I know about, but as far as I know it requires fairly large vectors to cause it to occur. I wouldn't bet millions of dollars on that guess.

didibus05:06:06

Interesting, I'm guessing it's non trivial to implement such data-structure

andy.fingerhut07:06:24

I have read the RRB vector paper, and done testing on about 4 different implementations of it. All that I have tested have bugs in the concatenation implementation, which is one of the trickiest parts to get right. I'm starting to doubt there is a way that is correct and guaranteed O(log N) time.

👀 4
andy.fingerhut02:06:34

I would think that for a to-do list, you aren't necessarily needing nanosecond-level response to changes from a UI in the order, and the lists aren't thousands of items long, so perhaps a Clojure vector/list, even with linear time required to arbitrarily insert elements at internal positions, might not actually be noticeable performance-wise?

didibus05:06:14

Ya, I mean I'm not even making a real Todo app, was just doing a short demo of writing one quickly in Clojure. First time I write a Todo app. Everything was going smoothly, but when I got to that part, I was a bit choked. Thinking... Hum, is there no way to immutably handle this case without constructing a whole new vector?

Eric Ihli02:06:54

Performance question.

(defn -merge
  ([xsa] xsa)
  ([xsa xsb]
   (concat
    (butlast xsa)
    (list (concat
           (last xsa)
           (first xsb)))
    (rest xsb))))
I'm doing this 2^n times. With n == 20, it's taking ~2.6 seconds. Is there a way I can speed that up? Type hints? Some java native thing?

hiredman03:06:27

How are you measuring? My guess is it is taking that long to print the result

Eric Ihli03:06:58

Using tufte to profile. Wrapping the function in a count so nothing gets printed.

hiredman03:06:11

Last is always going to kill, you could try swapping the inner concat for lazy-cat

hiredman03:06:13

If you write your own lazy-seq thing instead of using concat and friends like that you can take advantage of the struct of lazy seqs to do much better

Eric Ihli03:06:11

Hmm. Instead of a butlast followed by a last on the same thing, I wonder if a (split-at (dec (count xsa)) xsa) would be faster. Kill two birds with one stone. I'll try out the lazy-cat too.

hiredman03:06:38

(fn f [a b]
  (lazy-seq
   (let [f (first a)
         r (rest a)]
     (if (seq r)
       (cons (first a) (f (rest a) b))
       (cons (concat f (first b))
             (rest b))))))

🙏 4
hiredman03:06:44

using count with split-at as the same problem as using butlast and last

hiredman03:06:57

you have to traverse the seq multiple times

hiredman03:06:38

so you end up both not being lazy, and doing multiple traversals, where want you want to do a lazy traversal is such a way that those lazy traversals can be stacked, so no work is done until you force the final product, which does all the work and only does a single traversal

hiredman03:06:18

but there is a good chance your other code elsewhere is forcing seqs, so who knows

andy.fingerhut03:06:48

In the absence of hitting bugs in the rrb-vector library, that is the kind of thing it is made to be fast at, i.e. lots of split and concat. operations.

tio04:06:08

When a function looks like this, how does it work? For example; it seems like there are two bodies. Does it pattern match on one of them?

(defn env->UserCredentials
  "Create a UserCredentials instance from environment variables, defaulting to
  CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, and ACCESS_TOKEN_SECRET"
  ([]
   (env->UserCredentials {:consumer-key      "CONSUMER_KEY"
                          :consumer-secret   "CONSUMER_SECRET"
                          :user-token        "ACCESS_TOKEN"
                          :user-token-secret "ACCESS_TOKEN_SECRET"}))
  ([env-mapping]
   (let [credentials (map-values #(System/getenv %) env-mapping)]
     (assert (every? some? (vals credentials))
             (str "Failed to read credentials from env; one of "
                  (vals env-mapping) " could not be found."))
     (map->UserCredentials credentials))))

phronmophobic04:06:55

it will choose a body based on the number of arguments passed in (the function’s arity)

☝️ 8
tio04:06:40

Awesome and why does this function name have a -> in it? Is it some kind of convention for something?

phronmophobic04:06:05

yes, just convention

raspasov04:06:32

I think it’s a convention for “to” or “transforms into”

4
raspasov04:06:46

so “env to User Credentials”

tio04:06:48

Ah cool; that makes sense.

tio04:06:51

Thank you! 🙂

raspasov05:06:24

@tiotolstoy the multi-arity is a common way for providing defaults (like in the example above); you could even have completely different behavior, but that’s less common (maybe even weird and not recommended)

tio05:06:29

Yep; personally in love with pattern matching on function arity. Just wanted to confirm that’s what I was looking at! @raspasov

tio05:06:39

Why can you not have many expressions inside the function body of main?

(defn -main
  [& arg]
  (for [user_id following_ids]
    (mute_user user_id)))

tio05:06:08

If I included (def user_id ….) above for it would error out with some error about inline-def

hiredman06:06:15

I'd suggest taking those kind of questions to #beginners

4
hiredman06:06:37

Def in clojure always creates a top level global definition, not a definition in whatever scope you are in, so it almost always a mistake to nest it in some other scope

tio06:06:26

OK sorry hiredman. I’ll use beginners next time 🙂

seancorfield06:06:05

@tiotolstoy The main reason for that is that in #beginners folks either are beginners or have opted-in to helping beginners so the patience and tolerance is a lot higher there. The expectation for #clojure is that folks have a solid knowledge of the language and tend to be asking more esoteric questions that are likely to have opinion-based answers...

tio06:06:45

Understood Sean 🙂

wombawomba09:06:26

I’m running into a weird macro-related problem. Here’s a minimal example to reproduce:

(def data (constantly true))

(defmacro def-fn [] `(defn ~'foo [] ~data))

(def-fn) ; throws java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__5672
Any idea what’s causing this and how I can work around it? In the actual code I don’t control data, so any eventual workaround would have to live in def-fn.

delaguardo09:06:51

you could change the macro to be more dynamic

(defmacro def-fns [] `(do ~@(for [i (range (count things))] `(defn ~'foo [] (get things ~i)))))

wombawomba09:06:50

yeah okay that’s not a bad idea

delaguardo09:06:19

your updated example will expand into

(defn foo [] #function[clojure.core/constantly/fn--5672])
then compiler need to evaluate it but the body is already evaluated into class

wombawomba09:06:07

In my actual application though, I actually want to do something more like:

(require '[schema.core :as s])
(def data (s/constrained s/Any (constantly true)))
(defmacro def-fn [] `(s/defn ~'foo [x :- ~data]))
(def-fn) ; throws java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__5672

wombawomba09:06:54

I don’t see how I can make something like that happen without resolving data in the macro

delaguardo09:06:03

just leave data unquoted

(defmacro def-fn [] `(s/defn ~'foo [~'x :- data]))
also note the ~' before x

wombawomba09:06:58

awesome, thanks!

wombawomba09:06:14

what if I also want to generate multiple things?

(def data [(s/constrained s/Any (constantly true))])
(defmacro def-fns [] `(do ~@(for [x [data]] `(s/defn ~'foo [~'x :- ~x]))))
(def-fn)

wombawomba09:06:00

(let’s pretend I’m coming up with unique names for each thing)

delaguardo09:06:26

same trick as I described before

(defmacro def-fns [] `(do ~@(for [i (range (count data))] `(s/defn ~'foo [~'x :- (get data ~i)]))))

delaguardo09:06:36

to make fn names unique - just generate the symbol based on i or any other unique identifier and use it

wombawomba09:06:59

thanks a lot!

wombawomba09:06:37

actually….

wombawomba09:06:50

that works, but I can’t understand why 🙂

delaguardo09:06:14

(defmacro def-fns [] `(do ~@(for [i (range (count data))] `(s/defn ~(symbol (str "foo-" i)) [~'x :- (get data ~i)]))))
as an example

delaguardo09:06:10

which part is the most confusing?

wombawomba09:06:21

actually, I played around with this a bit and I think I’ve got it now, thanks 🙂

delaguardo09:06:42

the core is simple: the only difference between macro and function — macro works during Read not Eval in Read/Eval/Print/Loop

delaguardo09:06:00

cool! happy hacking!

wombawomba09:06:29

I guess the main thing for me was that I didn’t realize I could just pass arbitrary expressions as schemas to s/defn,

wombawomba09:06:48

yeah, thanks again :)

rmxm09:06:11

how popular is memoization in clojure? I am looking at a case: There is a service that returns some configuration details, the service is http and stateless. I see in the code they call the service during bootstrap, repack the values into more usable structure and keep that in atom. I was thinking this should be memoized, but then I still have a bunch operations on raw data happening everytime someone wants a particular piece of config, so I would have to memoize all helper functions that access the config?

hyankov09:06:22

Enough to have a general purpose function in the core: https://clojuredocs.org/clojure.core/memoize 🙂

rmxm10:06:34

yeah I am aware of that function, I am seeking guidance in how to organize such thing with the situation explained above... I know I can easily memoize some http response, however afterwards there is some processing of the data into a more resonable structure - so I guess I should memoize this also?

hyankov10:06:27

The http service can be stateless but that doesn't mean it's referentially transparent. I assume it will return different configurations when called over time. Memoization can be used as some sort of caching technique but with caching you end up with a different problem - when do you invalidate the cached results. For the other part: > I still have a bunch operations on raw data happening everytime someone wants a particular piece of config As long as you have these functions in the shape of (f config raw-data) and the functions are pure - yes you can memoize those. Keep in mind that the memory usage will grow linearly with the number of invariants so it's speed/memory trade-off.

lmergen10:06:45

i'm a relative macro noob, but i'm trying to write some wrapper macros around spec and finding it... challenging. specifically, i'm trying to dynamically generate the keys that i want to define specs for, and i'm being confronted with s/def itself being a macro, and not capable of understanding variables example, this works:

(defmacro def-spec-test1 [k]
  `(s/def ~k string?))
the macroexpand shows that it is forwarding to the s/def macro, and properly expanding k into the argument i provide. however, for sake of demonstration, let's say we want to dynamically generate some keys, or reassign them:
(defmacro def-spec-test2 [k]
  `(let [k# ~k]
     (s/def k# ~string?)))
this then expands into
(let* [k__29626__auto__ :foo/bar] (clojure.spec.alpha/def k__29626__auto__ #function[clojure.core/string?--5410]))
which makes sense but of course is not what i intended. i suspect there's probably a dynamic inside the let block that i'm unaware of, but is it possible at all to do something like this ?

delaguardo10:06:30

you can generate keys inside macro but outside of expansion form

(defmacro def-spec-test2 [k]
  (let [key (generate-key k)]
    `(s/def ~key ~string?)))

delaguardo10:06:52

where generate-key is a function returning a keyword

lmergen10:06:50

let me see

wombawomba13:06:39

Is anybody here using any of the ‘complementary’ util libraries, such as Specter, Medley, or Tupelo? If so, how’s it working out for you?

p-himik13:06:15

Using Specter on the backend for a few tasks, works just fine. Initially was using it on the frontend as well, but stopped because its size is unacceptable for something that works in browsers.

aisamu13:06:29

I've seen a bunch of places that just copy the functions of interest (such as map-vals from medley).

wombawomba14:06:12

alright, thanks 🙂

wombawomba14:06:21

the size aspect is interesting.. I hadn’t considered that

Daniils Petrovs15:06:55

Does anyone here have experience with DynamoDB and https://github.com/cognitect-labs/aws-api ? I am using the Scan operation, but I get Items in the response in the DynamoDB format, was wondering if there was a simple way of unmarshalling like in the JavaScript sdk?

Cory15:06:15

might consider using https://github.com/Taoensso/faraday or reviewing it to see how it handles the problem

Daniils Petrovs15:06:43

ok, will give it a try, thank you!

ghadi15:06:06

Amazon provides augmented SDKs for certain services (some examples are KMS, DynamoDB, Kinesis). Those service-specific libraries handle service-specific concerns (e.g. key-stretching and caching in KMS, consumer state management in Kinesis)

ghadi15:06:16

aws-api does not attempt to handle things that are not written down in Amazon's API service descriptors. The DynamoDB storage payload format is not in those descriptors.

ghadi15:06:22

you can make a helper function that converts a clojure map {:foo "bar"} into {"foo" {"S" "bar"}}, which Dynamo payloads require and vice versa

4
ghadi15:06:04

AFAIK, this is the only special handling that DynamoDB requires (besides respecting rate-limits, which applies to all AWS apis, but moreso to DDB) @thedanpetrov Happy to continue discussion in #aws

rickmoynihan16:06:25

Ooo I’ve been looking forward to reading this for some time! 👀

Lennart Buit18:06:38

Cool; publicly available!

jdkealy17:06:10

Is it possible to use derive for multimethods when the accessor is a string? e.g. (derive "square" "rect") Or if not, is there an efficient way to coerce the string accessor to a keyword? e.g. (get-area {:shape "square", :w 10, :h 10}) and have that use the (defmethod get-area :rect (fn [{:keys[ w h] (* w h ) }

noisesmith17:06:23

if you need to derive based on a string in the data, I'd call keyword inside the defmulti dispatch function

noisesmith17:06:58

because derive needs a keyword or symbol, it doesn't work with string

noisesmith17:06:42

eg. (defmulti get-area (fn [{:keys [shape]}] (keyword shape)))

jdkealy17:06:11

it seems it needs a fully qualified keyword though? (defmulti get-area (fn [{:keys [shape]}] (keyword (str *ns*) shape))) (derive ::square ::rect) (defmethod get-area ::rect (fn [{:keys[ w h] (* w h ) }) (get-area {:shape "square" :w 100 :h 100})

jdkealy17:06:52

without :: i get java.lang.AssertionError: Assert failed: (namespace parent) when trying to use derive

noisesmith17:06:59

well, you added the ns to the keyword in the defmulti - are you sure it doesn't work without it?

noisesmith17:06:29

oh I see, derive rejects a non-namespaced keyword, I'd forgotten this!

teodorlu18:06:58

What's a nice way to def with metadata? I want to re-export some functions from a different namespace.

noisesmith18:06:38

you can specify a metadata map as an arg to intern, you could literally apply the metadata of the original to the re-export on definition

user=> (intern *ns* (with-meta 'j (meta #'clojure.string/join))  clojure.string/join)
#'user/j
(ins)user=> (doc j)
-------------------------
user/j
([coll] [separator coll])
  Returns a string of all elements in coll, as returned by (seq
coll),
   separated by an optional separator.
nil
*edit - better example

noisesmith18:06:23

but re-exporting has many gotchas, and it's often more straightforward to design your namespaces differently rather than having a facade

teodorlu18:06:35

@isak Thanks! That seems to do exactly what I was asking for 💯

4
teodorlu18:06:12

@noisesmith > it's often more straightforward to design your namespaces differently rather than having a facade Can I dig deeper in this line of thinking somewhere?

noisesmith18:06:25

this discussion covers it well I think, with @tbaldridge representing the viewpoint I'd advocate https://groups.google.com/forum/#!msg/clojure/Ng_GXYSrKmI/q0cxahB3BAAJ

👀 4
4
🙏 4
noisesmith18:06:22

you will likely recognize a few names in that discussion

noisesmith18:06:10

also my example with intern above works around the complaints in the thread about go to source finding the wrong location, and transitive dependency bugs

noisesmith18:06:29

(though I don't even recommend that - I just think it's better than the magic import-vars attempts)

isak18:06:04

TLDR: learn to love 10K line files, it's fine

noisesmith18:06:34

that's not what is advocated in that thread by Timothy at all

isak18:06:07

You don't see how the consequence would be larger namespaces? (I don't have a problem with it)

noisesmith18:06:50

> Anything not in the API that should be unpublished to users is in other namespaces that are imported and wrapped by vars in the main namespace. the difference is that it's done by making a new definition defining the API, not re-exporting the var from another ns

noisesmith18:06:50

the idea of having a layer that doesn't expose details that you don't want people relying on is fine (it's a fundamental principle of structured programming) - the difference is a tactical one, the mechanism for doing that layer

isak18:06:40

Ok, fair enough

p-himik19:06:44

Is there a more reasonable way to add a self-referencing multi-arity multimethod than to either: - Create a separate multi-arity function and then use that function, e.g. (defmethod f :x ([_ a] (other-f a)) ([_ a b] (other-f a b))) - Call (.addMethod multifn ...) manually ?

p-himik19:06:31

And I'm not sure about the second option - feels like clojure.lang.MultiFn may be an implementation detail.

noisesmith19:06:51

yeah, I would find that very suspicious

noisesmith19:06:01

what is "self-referencing' about this?

p-himik19:06:50

I meant turning something like

(defn x
  ([a] (x a 1))
  ([a b] ...))
into a multimethod.

noisesmith19:06:09

if every arity calls other-f

(defmethod f :x [_ & args] (apply other-f args))

noisesmith19:06:30

I think that aligns precisely with the intent

Alex Miller (Clojure team)19:06:00

I think you really want to do this in the dispatch method

noisesmith19:06:25

what would that look like?

Alex Miller (Clojure team)19:06:58

or I guess I would probably wrap it around the multimethod

p-himik19:06:43

I don't control the calls to the multimethod.

p-himik19:06:26

To make it more concrete - I'm adding a new function to HoneySQL. Yet another alternative is to use the ugly [x y & args] arity and check the args manually in the body for the third value.

Alex Miller (Clojure team)19:06:08

but you could do something like this:

(defmulti foo (fn [& args] (second args)))
(defmethod foo nil [a] (println "1 arity" a))
(defmethod foo :default [a b] (println "2 arity" a b))
(foo 5 2)
(foo 5)

Alex Miller (Clojure team)19:06:38

I don't know what you want to dispatch on

Alex Miller (Clojure team)19:06:56

but multimethods that take varargs can dispatch to methods with different arities

p-himik19:06:04

Yeah, the dispatch value will be the very same for both arities.

Alex Miller (Clojure team)19:06:23

well you can invent any dispatch value you like and tag it

noisesmith19:06:45

if you are extending honeysql, the multi dispatch isn't actually something you can control right?

Alex Miller (Clojure team)19:06:56

are you doing different things for different arities or just patching missing values?

hiredman19:06:49

Demethod's fntail can, if I recall, include a local name for the fn

Alex Miller (Clojure team)19:06:13

they can, although I don't think that helps you in any way here

p-himik19:06:21

@U064X3EF3 Your question about different things made me realize that I can just wrap a single-arity function in a multi-arity multimethod:

(defn other-f [a b]
  ...)

(defmethod f :x
  ([_ a] (other-f a 1))
  ([_ a b] (other-f a b)))
Yeah, definitely bedtime.

Alex Miller (Clojure team)19:06:03

no idea why you wouldn't do that in other-f itself

hiredman19:06:09

or just

(defmethod f :x other-f
  ([_ a] (other-f a 1))
  ([_ a b] (other-f a b)))

p-himik19:06:41

@U0NCTKEV8 Doesn't work: "Unable to resolve symbol: f-x in this context".

p-himik19:06:06

@U064X3EF3 Sorry, no idea what you mean. I don't call other-f anywhere. The library code calls f for me, I don't control it.

hiredman19:06:45

user=> (defmulti f :foo)
#'user/f
(defmethod f :x other-f
  ([_ a] (other-f a 1))
  ([_ a b] (other-f a b)))
#object[clojure.lang.MultiFn 0x7b44b63d "clojure.lang.MultiFn@7b44b63d"]
user=>

p-himik19:06:12

And now try calling it.

hiredman19:06:15

user=> (defmulti f (constantly :x))
#'user/f
(defmethod f :x other-f
  ([_ a] (other-f a 1))
  ([_ a b] (other-f a b)))
#object[clojure.lang.MultiFn 0x301d8120 "clojure.lang.MultiFn@301d8120"]
user=> (f 1 2)
Execution error (StackOverflowError) at user/eval140$other-f (REPL:2).
null
user=>

p-himik19:06:06

So the right way to do this would be to

(defmethod f :x other-f
  ([_ a] (other-f nil a 1))
  ([_ a b] (do-whatever-to a b)))

p-himik19:06:05

OK, I still don't get why

(defmethod f :x f-x
  ([x] (f-x x :y))
  ([x y] (println "f-x" x y)))
gives me Unable to resolve symbol: f-x in this context when I'm trying to use it as (f :x).

hiredman19:06:23

Are you not using clojure?

p-himik19:06:35

Heh, I am using Clojure.

p-himik19:06:14

The next to last code block with _ and other-f works. The last code block with f-x doesn't. Restarted REPL before trying either.

hiredman19:06:27

What does the stacktrace say? Do you have some other macros rewriting your code?

hiredman19:06:46

I ask in case you using clojurescript

p-himik19:06:14

$ clj -r
Clojure 1.10.1
user=> (defmulti f (fn [x & args] x))
#'user/f
user=> (defmethod f :x f-x ([x] (f-x x :y)) ([x y] (println "f-x" x y)))
#object[clojure.lang.MultiFn 0x72458efc "clojure.lang.MultiFn@72458efc"]
user=> (f-x :x)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: f-x in this context
user=> *e
#error {
 :cause "Unable to resolve symbol: f-x in this context"
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message "Syntax error compiling at (1:1)."
   :data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "NO_SOURCE_PATH"}
   :at [clojure.lang.Compiler analyze "Compiler.java" 6808]}
  {:type java.lang.RuntimeException
   :message "Unable to resolve symbol: f-x in this context"
   :at [clojure.lang.Util runtimeException "Util.java" 221]}]
 :trace
 [[clojure.lang.Util runtimeException "Util.java" 221]
  [clojure.lang.Compiler resolveIn "Compiler.java" 7414]
  [clojure.lang.Compiler resolve "Compiler.java" 7358]
  [clojure.lang.Compiler analyzeSymbol "Compiler.java" 7319]
  [clojure.lang.Compiler analyze "Compiler.java" 6768]
  [clojure.lang.Compiler analyze "Compiler.java" 6745]
  [clojure.lang.Compiler$InvokeExpr parse "Compiler.java" 3820]
  [clojure.lang.Compiler analyzeSeq "Compiler.java" 7109]
  [clojure.lang.Compiler analyze "Compiler.java" 6789]
  [clojure.lang.Compiler analyze "Compiler.java" 6745]
  [clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6120]
  [clojure.lang.Compiler$FnMethod parse "Compiler.java" 5467]
  [clojure.lang.Compiler$FnExpr parse "Compiler.java" 4029]
  [clojure.lang.Compiler analyzeSeq "Compiler.java" 7105]
  [clojure.lang.Compiler analyze "Compiler.java" 6789]
  [clojure.lang.Compiler eval "Compiler.java" 7174]
  [clojure.lang.Compiler eval "Compiler.java" 7132]
  [clojure.core$eval invokeStatic "core.clj" 3214]
  [clojure.core$eval invoke "core.clj" 3210]
  [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
  [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
  [clojure.main$repl$fn__9095 invoke "main.clj" 458]
  [clojure.main$repl invokeStatic "main.clj" 458]
  [clojure.main$repl_opt invokeStatic "main.clj" 522]
  [clojure.main$repl_opt invoke "main.clj" 518]
  [clojure.main$main invokeStatic "main.clj" 664]
  [clojure.main$main doInvoke "main.clj" 616]
  [clojure.lang.RestFn applyTo "RestFn.java" 137]
  [clojure.lang.Var applyTo "Var.java" 705]
  [clojure.main main "main.java" 40]]}
user=> 

hiredman19:06:30

You have to invoke the multi method

hiredman19:06:10

f-x is only bound in the bodies of that method

p-himik19:06:10

So the reduced arity will result in double dispatch?

p-himik19:06:13

OK, then I don't understand. And then why does the code with other-f work? It seems to be almost the same.

hiredman19:06:14

That name is bound to just to the function that implements that method, so invoking it does not go through the multimethod

hiredman19:06:11

That name is just like f in (fn f [] )

p-himik19:06:15

Whoa, hold on. letfn and defn made me sure that providing a name to fn would also make it possible to self-reference it. That's probably the first really suprising unpleasant fact that I learn about Clojure.

p-himik19:06:06

No, I take it back - my brain is half asleep and fn does indeed make it possible to self-reference it.

p-himik19:06:15

So, macroexpanding that defmethod gives me

(clojure.core/fn f-x
  ([x] (f-x x :y))
  ([x y] (println "f-x" x y)))
And it works by itself. But it doesn't work if used in .addMethod..?

p-himik19:06:15

Oh god, of course. Terribly sorry for being so dense. Off to sleep.

kwladyka22:06:59

@seancorfield hi, I am trying to use your depstar library this work

clojure -A:depstar -m hf.depstar.uberjar app.jar
java -cp app.jar clojure.main -m api.core
with AOT not:
clojure -Spom
clojure -A:depstar -m hf.depstar.uberjar app.jar -v -C -m api.core
java -jar app.jar
Error: Could not find or load main class api.core
Caused by: java.lang.ClassNotFoundException: api.core
Maybe this is too late hour, but I don’t see what I am doing wrong. Besides of that do you think this is good idea to use AOT on production? Considering I am running jar file with the same version of java, because I use Dockerfile.

kwladyka22:06:01

while I am thinking about paying model dependent on start app time and processing time AOT start to be more interesting. I can build app even with Java 14 or 11

noisesmith22:06:18

the java version shouldn't make a difference - clojure doesn't use javac to emit bytecode

hiredman22:06:50

well, in theory it could make a difference because the compiler uses reflection to resolve methods

4
hiredman22:06:19

and sometimes you get subtile behavior changes across jvm version like arrays of methods being returned in different orders, etc

hiredman22:06:50

and new java features actually let you compile totally different classfiles for different java versions

seancorfield22:06:16

@kwladyka You're missing (:gen-class) in the ns of api.core.

✔️ 4
👏 2
kwladyka22:06:06

Oh I don’t know why I thought this is not needed anymore with deps.edn and depstar

seancorfield22:06:19

You're the second or third person to trip over this in the last week so I'm going to update the depstar README to clarify that.

seancorfield22:06:15

If you create a project with clj -A:new app api.core then clj-new adds (:gen-class) for you, just like lein new and boot new do for app projects.

kwladyka22:06:22

now it works 👍

seancorfield22:06:59

(if you use clj-new to create your app project, your deps.edn will already have an :uberjar alias to save you some typing)

kwladyka22:06:20

Do you recommend to use AOT in Dokcerfile so when I am sure java version is the same? I am not an expert in this topic. totally. It can gives me better performance maybe, so it sounds interesting. I think I didn’t try AOT before, so I have no opinion.

hiredman22:06:59

the only "performance" difference is you might get faster start times

kwladyka22:06:18

I want to use this with google run / functions, so this matter

kwladyka22:06:39

because it is real cost

seancorfield22:06:57

Whole-application AOT in order to improve startup time is a valid use.

kwladyka22:06:11

ok, I have impression people try to avoid AOT

kwladyka22:06:57

oh I will copy this solution with :uberjar in deps.edn

Cory22:06:10

AOT is preferred on the operations side of things, especially with serverless and containerized applications in the JVM.

hiredman22:06:45

aot is gross

kwladyka22:06:52

good, this is what I am planning to do

hiredman22:06:37

because you are generating and persisting bytecode to disk it can cause issues with clojure's more dynamic features. you can usual avoid those issues if do things like only aot for deployed artifacts and not locally while testing and running repls

👍 8
hiredman22:06:50

but then sometimes things will load fine in clojure, but break when you aot them, so then sometimes you check things in because it looks good and works locally, and then go to build and boom

kwladyka22:06:26

While I hear a lot of this things I don’t know any specific case which really happen to somebody

hiredman22:06:34

that said, these days you see a lot fewer people struggling with aot related issues

Cory22:06:55

that's what good CI/CD pipelines are for. i agree that it can impede local development.

kwladyka22:06:02

if this is blow up during compile to uberjar, then this should be fine

kwladyka22:06:38

ah in the context of PDF generation… wkhtmltopdf generate much different PDF (everything looks so bad) in docker, than in my local environment. Even while this is the same version of wkthmltopdf. I think I will try this alternative solutions faster, than I thought 🙂

Cory22:06:11

what distro? i've hit this before in node. it's tricky to get all the right fonts and styles in place.

kwladyka22:06:53

I am trying this with FROM clojure:openjdk-14-tools-deps-1.10.1.502-buster as deps

kwladyka22:06:52

this is much more broken, than only fonts. wrong sizes, broken pages, wrong width of content

noisesmith22:06:32

My first suspicion would be that you need to install all the required fonts, as it is using bad fallbacks. Some of those fonts might be non-free so unavailable from debian, you might need to hand install.

kwladyka22:06:22

I am going sleep, it is too late. Thank you all for your help. Good night!