Fork me on GitHub

what are all the main component libraries in use in recent days , sierra’s, mount, integrant/duct, am missing any ?


Here's another new comer too:

👍 1
Noah Bogart13:02:50

Juxt’s clip

👍 2

@jasonjckn Also and I think there's been another one pop up recently...

👍 2

@seancorfield do you have a preferred one yourself


We are big fans of Component at work.

👍 1
Drew Verlee03:02:33

Can someone explain at a high level why stuarts Component uses clojure meta data? This surprise me because i always thought meta was mostly useful for tooling. E.g editors. Because meta is data that isn't used in equality and i assume is complied out of prod builds. The later is obvious not true sense it's being used here...


Metadata is a first-class feature that many clojure.core functions will retain It would take a very explicit step to exclude metadata, e.g. setting and including specific keys there


Metadata is used a lot more extensively than you might think.


Being able to extend protocols by adding metadata to objects is a game changer, as far as I'm concerned, so I think you'll see the use of metadata increase now that's possible. @drewverlee

👀 1

Could you add more on why metadata is gaining more trend?

👍 1

@UGC0NEP4Y @U01G423GNFP Clojure 1.10 introduced the ability to satisfy protocols by adding metadata to (Clojure) objects: -- this means you can provide your own implementation to any such open protocol, attached to any Clojure object (that implements clojure.lang.IObj), and pass that object into code that relies on those protocols -- without statically extending the protocol to a type.


For example, with Component, you can use plain hash maps or even functions as "components" because you can attach start/`stop` lifecycle functions to them. next.jdbc does this so that it can offer (optional) support for Component, without needing to specify Component as a dependency (which would force it on other users):

👀 1
😮 1

You can see there it uses a plain, empty hash map for the component that you can call start on, and returns a function that you can call stop on.


next.jdbc itself has a number of protocols, several of which can be extended dynamically via metadata this way, including (`SettableParameter`) so that users do not need to extend that protocol to the various types of value they pass as parameters: if they need special parameter treatment, they can just attach it as metadata, in the call as needed. And next.jdbc.types provides metadata-based wrappers for all SQL types that you can use to wrap any object (`IObj`) passed as a parameter to have it set as a set as a parameter using that JDBC type instead of just .setObject().


In addition, I believe several Clojure databases are using metadata to attach additional information to entities -- while not breaking equality semantics -- to provide added data about the entity. That's a bit hand-wavy because I'm not familiar enough with them to point you to specifics (and I haven't confirmed it -- just going on comments I've seen going back and forth in some channels).


Another good example is datafy/`nav` (also new in Clojure 1.10) but I think you can argue that falls under "tooling" rather than "production" use. Still, it's yet another example of being able to satisfy arbitrary protocols, at runtime, without needing to extend them to all values of any given type.

Drew Verlee22:02:42

thanks sean, thats really interesting.


@seancorfield Thanks for the detailed explanation. That said, it seems protocol is flexible and power enough. Under what circumstance shall we use multimethod? Any good use case?


Protocol dispatch by type is fast (type of the first argument). Protocol dispatch via metadata is slower, but very flexible, and again it's off the first argument. Multimethods dispatch off arbitrary computed values and can work across all arguments. Both systems are open-to-extension (which is the Clojure way).


At work, we have a multimethod for handling email status updates, for example, because the action to take is based off two things: the new status update and the current status information -- can't be a protocol.


I feel multimethod is ONLY appropriate for some utility functions, for example, (defmulti print-me) which dispatches on the printed object, and the example you make also sounds like a utility function. Multimethod is NOT suitable for cases where: We have a certain object and we have several functions to dispatch on this object. It is of course doable, but not applicable because mutlimethod is so loose, it does not convey the concept that some functions belong together serving for a single purpose whereas Protocol can.


Multimethods have their place. We have 15 in a codebase of 130K lines. Some people are going to use them more. Those 15 defmulti have 164 defmethod between them. They all tend to be dispatch-on-value rather than dispatch-on-type.

👍 1

@drewverlee extend-via-metadata is a relatively recent feature in Clojure's history. you can extend any object that can carry metadata to implement a protocol now and component just added support for this feature: you don't have to use it, but you can.

👀 1

How do you relate reify with extend-via-metadata. I don’t quite understand how convenient extend-via is


extend-via-metada doesn't require you to make a bridge between the reified interfaces/methods that the object implements, the object itself is still just the object, you're just adding an ad-hoc implementation to it

👀 1

I didn’t see concrete examples that extend-via-metada brings in. Other than it breaks extends? and satisfieds?


I think it was implemented primarily for the Navigable protocol. Personally, I use it to make database query results Navigable (works in Reveal, REBL, etc.). Having to implement a custom type for that would suck.

thanks3 1

This is probably best example on how you can implement a Component lifecycle without a record and explicitly depending on Component :

👍 1
Søren Sjørup12:02:23

I’m looking for a generic comparison function that would allow comparison across different types like (compare 0 "") as you can with < and friends in Datomic. Does anyone know of a library that does this? seems not to be it…


Just a "maybe", haven't thought too much about it:

(defn compare+ [a b]
  (let [at (type a)
        bt (type b)]
    (if (= at bt)
      (compare a b)
      (compare (str at) (str bt)))))


Gotta handle nil separately since its type is also nil.

Søren Sjørup12:02:39

Yeah that’s how I started! 🙂 But that will not compare numbers correctly:

user=> (compare 0 0.0)
user=> (compare+ 0 0.0)
I started reverse engineering what’s in Datomic and I’ve gotten pretty far. I just wonder if someone else has done the same before.


Good point on numbers.

Søren Sjørup12:02:07

There is also a special case with vectors and lists.


Even that function might have some gaps in its implementation -- not sure.


But feel free to use it, extend it, change it, if it helps you with anything.

👍 1

This is what Puget uses internally

👀 1
Søren Sjørup17:02:48

@UDXEK491P thank you, it looks very similar to what I arrived at. Will try and share it.


Does anyone have any tips for writing unit tests w/ hato/jdk's HttpClient? I want to check what its called with and fabricate some responses. (starting a real http server to talk to is an option, just want to know if there are any options before that layer, and I am separately unit testing call sites w/ a classic protocol switch)

Ben Sless17:02:35

I'd just use a protocol on the client

James Amberger18:02:38

Why is (seq? [1 2 3]) false?


Because vectors don't implement ISeq. "A seq" and "a sequential collection" are not the same.


For the latter, there is sequential?.

James Amberger18:02:46

thanks. I’ll think I’ll have to review the relationships between coll , seq, sequential

Alex Miller (Clojure team)18:02:09

a vector is seqable, that is "can produce a seq", but is not itself a seq


Howdy folks 👋


Trying to get going with criterium for the first time, but as soon as I evaluate any benchmarking expression at the REPL, the Clojure process hogs up my CPU and hangs.


2019 macbook pro on catalina, criterium 0.4.6, clojure repl started via Calva jack-in


any idea what's happening?


Have you tried leaving it running for a few minutes on something simple like (inc 1)?


not yet, just been killing it after ~20 seconds


What time does a single run take?


(time (reduce + (map (fn [x] (/ x 100.0)) (range 100))))

"Elapsed time: 0.216805 msecs"


ope, got something w/ (inc 1)


maybe my expectations of how long it was supposed to take were just off...


There is quick-bench in addition to bench


> (c/quick-bench (inc 1))

Evaluation count : 55276344 in 6 samples of 9212724 calls.
             Execution time mean : 3.319750 ns
    Execution time std-deviation : 0.881184 ns
   Execution time lower quantile : 2.238889 ns ( 2.5%)
   Execution time upper quantile : 4.306481 ns (97.5%)
                   Overhead used : 7.096611 ns


Ok, now the bigger reduce call is benchmarking faster


I guess I was just too impatient 😅


IIRC, the default time might be 60 sec, which only begins after an ~10-sec warmup time.

👍 1

The defaults can be changed with the appropriate parameters.