Fork me on GitHub
Yehonathan Sharvit08:12:15

A question about functions and their metadata: Why is the metadata of a function (e.g. docstring) attached to the var that refers to the function and not to the function itself?

Timur Latypoff08:12:20

I’d go philosophical and say that it’s not metadata if a function, but metadata of the var. You can attach metadata to an anonymous function, but what is the practical use of it?

Yehonathan Sharvit09:12:50

Yeah but with metadata on vars, in case you need to create a var that refers an existing function, you loose metadata.

(defn foo "A func" [])
(def bar foo)
(:doc (meta #'bar)); => nil

Timur Latypoff09:12:49

You’re right. Looks like it was just the simpler way to do it initially and simpler to understand (not to remember which properties belong to var, like :dynamic, and which belong to fn). def’ing the functions with another name also breaks many editors’ intellisense, so I guess it is just assumed that if you’re doing it, you know the implications and know how to deal with them :)


I'd say that if you (def foo bar) you explicitly state that you want a different name. If you wanted the same thing, you could have used the same thing. It makes sense to me that different names have different metadata.


A reason to create a different name might be to add docs, to add metadata.


(not entirely confident about this line of thinking, I'm not sure I'm right)


> Why is the metadata of a function (e.g. docstring) attached to the var that refers to the function and not to the function itself? Because vars can contain any kind of Object, not limited to functions. Not all Objects are IMetas. So a different approach wouldn't be nearly as homogeneous


(def bar #'foo) is a bit better, although :doc, :arglists metadata won't be copied. It's possible to create a thin macro that does so (the Potemkin lib does that, but also a lot more which is why it fell out of fashion)


I am currently working on this library and I allow to customize it using mulitmethods: I haven't seen any other libraries adding customizations with multi-methods, and I was wondering if there is a reason for that e.g. some better approach?


At least HoneySQL does this. But in v2 @U04V70XH6 has made a switch from multimethods to atoms. I don't know why though. I know of two potential reasons not to use multimethods, or at least not to use them directly: - They're slower (which is a cost with no gain if you don't need their flexibility) - There might be a need to def more than one method to extend something in a meaningful way. In this case, you either have to put the onus on the users to make sure that they call defmethod for all the required multimethods, or you have to create an interface that allows to do it with one call thus removing the potential to leave the system is a partially customized state, but it will hide multimethods from users so you might as well use something else

Alex Miller (Clojure team)14:12:57

Multimethods are not significantly slower


Ah, another tiny reason - users will have to make sure that the namespaces with all the relevant defmethod calls are loaded before the very first usage of your library.

Alex Miller (Clojure team)14:12:16

They used to be a lot slower due to a gap in default path caching that gave them that reputation but that has now been long fixed

👀 3

That's good to hear, thanks!


Thank you for adding all this context to my question, it helped me a lot!


I have this question that is kinda bugging me, it's not strictly related to clojure but it's part of a clojure project. Currently I'm running a hobby project on docker and I have a frontend server that pushes messages to a backend node (via rabbitMQ) that computes stuff and the UI will poll the frontend server for the result. At some point the backend runs some tasks that use up all of the CPUs. I was hoping to scale horizontally on the number of backend nodes, but now that I think about it, since all containers could do the same heavy task at once this could kill my CPU, right? Would it be a better idea to just have multiple consumer threads in one clojure process and schedule around a fixed thread pool?


java threads plus immutable data already gives you a lot of isolation, the difference is downtime and redundancy scaling, I'd split things based on their coupling in infrastructure scaling


it's definitely a win to put more things in one clojure process, since the jvm and clojure itself have significant overhead


Thanks for the insight. It's a hobby project but I don't really want it to go down as soon as it gets the slightest traction. I think I'll split the CPUs into two JVMs for at least some redundancy and then create 1 consumer per thread and use a shared claypoole pool for the heavy tasks


one approach is to have a generic "worker" which can do tasks for various parts of the system, with a shared input (via a queue service), it's like the normalized version of the program


of course, for something that will only be for a hobby where downtime doesn't matter? you could put it all in one jvm

👆 3
Andrew Byala22:12:54

Ok, this will sound a little silly, but could someone please verify something for me? I think the last example in the is incorrect, in that it claims to return a list of vectors instead of a vector of lists. I would appreciate a sanity check before I change things! What it says:

(def split-by (juxt filter remove))

(split-by pos? [-1 -2 4 5 3 -9])
=> ([4 5 3] [-1 -2 -9])
What I believe happens:
(def split-by (juxt filter remove))

(split-by pos? [-1 -2 4 5 3 -9])
=> [(4 5 3) (-1 -2 -9)]

James Kozianski22:12:50

I get the same result as you, and that seems to be aligned with what the docs for juxt say

Andrew Byala22:12:11

Thanks! Doc updated.