Fork me on GitHub
#clojure
<
2022-09-05
>
Ertugrul Cetin16:09:49

Why does Clojure generate too many .class files? It turns out Clojure creates a class file per function. As a result, you get too many .class files. My question is, why does it generate that many .class files instead of creating a class per namespace and having all functions inside that .class file? At least for AOT compilation, that'd be great merging those .class files? I'd like to know the technical reason behind it.

jpmonettas16:09:26

That is probably because the compilation unit of Clojure is a form, not a file or a namespace

ghadi16:09:29

what pain is experienced by “too many” class files?

Ertugrul Cetin17:09:22

> what pain is experienced by “too many” class files? I was working on byte code to wasm - so there were too many class files that caused some problems

ghadi17:09:35

what problems?

borkdude19:09:51

Why do multi-methods have "prefer-method" but there is no such thing for protocols? Can I have a multi-method behave similar to a protocol with respect to conflicting values?

borkdude19:09:36

As an example:

$ clj -M -e "(defprotocol Interceptor (interceptor [x])) (extend-protocol Interceptor clojure.lang.IRecord (interceptor [_] :rec) clojure.lang.IPersistentMap (interceptor [_] :map))  (defrecord Foo []) (interceptor (->Foo))"
This prints :rec - but when I implement interceptor as a multi-method it starts to complain:
$ clj -M -e "(defmulti interceptor type) (defmethod interceptor clojure.lang.IRecord [_] :rec) (defmethod interceptor clojure.lang.IPersistentMap [_] :map)  (defrecord Foo []) (interceptor (->Foo))"

Multiple methods in multimethod 'interceptor' match dispatch value: class user.Foo -> interface clojure.lang.IPersistentMap and interface clojure.lang.IRecord, and neither is preferred

hiredman19:09:45

I think in the case of the protocol fast path that uses java interfaces, supporting some preference mechanism there might have a large performance penalty

hiredman19:09:30

I am pretty sure there is a jira somewhere

borkdude19:09:19

What I'm looking for is actually to disable the preference mechanism for my defined multi-method, to get the same behavior

skylize19:09:33

How (if at all) do people normally document functions that might throw? (Especially in the case of exceptions bubbling up from Java, since that it is not self-documenting.)

(defn foo [bar] (.iMightThrow SomeJavaThing. bar))
Would you put it in a docstring? if so in what format? Maybe attach some metadata?
(defn ^{:exceptions ['java.lang.IOException]} foo ...)
Just leave a comment? Something else?

dpsutton19:09:00

You can see how clojure.core documents this:

clojure.core/even?
([n])
  Returns true if n is even, throws an exception if n is not an integer

skylize19:09:09

Good data point. Is that what you would do too?

dpsutton19:09:32

Yeah. There's no exception checking so putting it in metadata is making it more difficult for humans and easier for programs when no program checks it

vemv05:09:47

For this sort of reason I'd favor Results over exceptions where possible. You can integrate Results with spec or malli, whereas exceptions will completely escape those "type systems", which drastically decreases their usefulness IMO

Drew Verlee06:09:13

What do you mean "Results"? Does that mean just returning a value. E.g hashmap?

vemv06:09:56

nope, but almost. it's a wrapper which can be a hashmap https://en.wikipedia.org/wiki/Result_type (or you can return the same hashmap you would usually return - if it lacks an :error key, you could infer it's not an Error) there are a few libs like this in clj (you can google for "railway oriented clojure site:http://github.com"). and it's simple enough to roll your own

amithgeorge07:09:32

https://github.com/fmnoise/flow is a recent simple enough library that supports writing Railway like code.

👍 1
vemv08:09:21

Flow is nice, although it's exception-based which goes against my initial point IME using spec/malli/schema in large codebases, exception handling just cannot be an afterthought, else your investment will be less fruitful where it matters the most (if someone gets to knot between Flow and spec/etc... great :) I'm afraid that it doesn't exist today, and seems a hard problem to solve)

amithgeorge08:09:57

Not sure what you mean by "exception based being against the initial point". The exception (either created by Java code or created by us, possibly without the overhead of a stack-trace) is returned as a value. The exception class is reused as a placeholder for a failure value.

vemv08:09:54

If I got its readme correctly, it still encourages you to throw things

amithgeorge08:09:22

ex-info and the library's own fail-with only create an Exception instance. Most of the examples simply return the exception instance as a value. Some of the examples in the readme do use a throw to demonstrate that if some code does throw an exception, the libraries functions will catch the exception and return it as a value.

vemv08:09:46

So it seems both approaches are allowed? throw or return an ex. That flexibility is cool but I'd favor banning exceptions from business code altogether (also, sorry if I deviated this thread a little)

skylize15:09:04

I do not think exceptions and throwing of those exceptions are equivalent. An exception is really just another data structure, with semantic indication of failure, and a stack trace that can potentially be very useful for diagnosing said failure. Throwing that exception is like running into a theater screaming "fire!" A thrown exception is a stop-the-world demand for a attention with no early warning from the function signature. So there is definitely promise in catching exceptions to pass along as values (e.g. wrapped in what you call a Return type), instead of implicitly rethrowing. But sometimes when code is a middle-man between a consumer and something that throws, it seems sensible to just pass that along, instead of trying to handle it with insufficient knowledge. Maybe the consumer will use your function in a way that cannot possibly trigger an exception. Maybe the usage of a function is clear enough that you can realistically enforce compliance through convention alone (and the exception just alerts you that your code is obviously broken). Wrapping up the possibility of an exception that will never actually occur is just noise and boilerplate on both ends. It doesn't seem that unreasonable to sometimes trust your consumer to decide whether the additional structure is needed.

Sam Adams22:09:09

Anyone know if there’s a macro helper out there which will take the args to defn and give me the params vector(s)?

p-himik22:09:08

No such thing in core. But the code of defn is rather easy to follow.

👍 1
Sam Adams23:09:40

True, I’ll be a little surprised if there’s not a popular macro-helper lib with such a thing, but maybe there isn’t

dpsutton23:09:49

I think there's a clever use of spec to give you this

👍 1
amithgeorge07:09:25

https://github.com/galdre/morphe is an interesting take on AOP - wrapping functions with cross cutting concerns.

👍 1