clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
em 2021-03-07T00:21:44.163400Z

Cool problem! Your solution seems fairly compact, and as long as you're not looking for automatic clause combining (stuff like collapsing (< x 20) (< x 10) ) I'm not sure going all the way to rules engines is worth it. That said, if you're already using resolve and symbols, you could consider just using clojure's homoiconicity for your clauses. I'm thinking about the case where you might need to access more than one piece of data for a clause (like comparing (< :x :y) for x and y in the input value). What about an alternative representation like [accessor-map comparison]? E.g.

(def clauses '[[{:price price} (< price 2000)]
               [{:offer offer :ceiling ceiling} (= offer ceiling)}])
Advantages here is just using very readable code as the clause, and the implementation for this would be fairly trivial and would guarantee one-time access on all keys (you would just merge all the accessor-maps into a lookup, and bind/replace symbols for each clause). If the map notation is a little wordy, you could also have an implicit rule like :keys in destructuring, where if the accessor-map is a single keyword/symbol, then that's both the keyword/symbol (i.e. [:price (< price 2000)] instead.

2021-03-07T00:34:47.163800Z

Very interesting suggestion, thanks!

quadron 2021-03-07T01:52:25.169Z

would it make sense for packages to include the version in their namespace? would it not be more in accord with the idea of immutability and robustness? why should my project break if some implicit dependencies lose consistency over a simple update?

quadron 2021-03-07T01:56:02.171200Z

i would understand if this limitation is due to the legacy way of doing things in production

2021-03-07T02:00:40.174600Z

Rich Hickey didn't recommend keeping immutability of code -- he recommended that updates to code should not break the API provided by previous versions, i.e. you can add new functions or other kinds of Vars, or extend their capabilities to require less in terms of inputs, or provide more as return values, but not vice versa. https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/Spec_ulation.md

2021-03-07T02:02:09.176Z

If you want to break the current API, then you should consider creating a new name for the library and its namespaces, so it is a new thing, not a broken version of the old thing.

quadron 2021-03-07T02:02:47.176700Z

@andy.fingerhut are there any ecosystems that consider the version to be part of the namespace?

quadron 2021-03-07T02:03:01.177200Z

@andy.fingerhut does it make sense to have such a thing?

2021-03-07T02:04:07.178500Z

One way to provide a new library that is similar to an old one is to put a number in its name, e.g. the first version was mygreatlib, and when you want to break the API of mygreatlib, you instead publish mygreatlib2. I believe the talk I linked above mentioned a few cases of Windows APIs where this was done, but it was pretty rare.

quadron 2021-03-07T02:07:02.180Z

@andy.fingerhut but say different packages dependencies are pulling different versions of the "same" library, would there not be a conflict?

2021-03-07T02:09:02.181800Z

Depending upon what mygreatlib and mygreatlib2 do, they might conflict with each other, e.g. they both open a network socket listening on the same TCP port, or might write to a config file with the same name, etc., but in many cases there would be ways to prevent such conflicts. I could imagine some library functionality where it doesn't make sense for them both to be loaded at the same time in the same program, and some where it would make sense.

2021-03-07T02:10:34.183300Z

If they have different names like mygreatlib and mygreatlib2, most package management systems will treat them as different libraries that can both be depended upon. Whether they can both be used from the same program is a separate question. Most package management systems I am aware of that have library names and version numbers do not let you depend on both mygreatlib version 1.1 and mygreatlib version 1.2 at the same time -- you must pick one.

em 2021-03-07T02:14:36.186300Z

Slightly off topic but immutability in code might be one of those things that needs a lot more of the language built up around it, like how you wouldn't be doing as much functional/immutable programming in Java. Interesting experiment on this very idea is Unison lang https://www.unisonweb.org/

quadron 2021-03-07T02:15:14.186900Z

hmm, but isn't it bad practice to hide such stateful actions in the behaviour of a namespace? i suspect that in any case the namespace shouldn't open files or ports implicitly without knowledge of the caller

quadron 2021-03-07T02:17:16.187900Z

what you described sounds like a bug to me, it does not justify the expectations of the package manager

quadron 2021-03-07T02:20:32.188700Z

come to think of it... how do linux package managers treat this?

quadron 2021-03-07T02:22:40.189200Z

@eagonmeng unison seems like a hardcore implementation of what i had in mind

2021-03-07T02:24:43.191Z

If the library you are using is intended to help serve web requests, then it is supposed to listen on TCP sockets. In that particular case it is likely that merely loading the library doesn't open up sockets -- you would have to make an explicit call, and in most cases you could probably provide an optional, or required, parameter for the TCP port number to listen on, so even then two differently named libraries that do that might not conflict with each other.

2021-03-07T02:25:22.191700Z

Linux package managers and Clojure Leiningen/deps.edn/boot are doing related, but still quite different things.

2021-03-07T02:25:51.192300Z

There are Linux packages that conflict with each other, even if they have completely different names. Sometimes this is documented, sometimes not.

quadron 2021-03-07T02:35:28.193400Z

@eagonmeng i am not sure if the namespace/version issue is addressed there

em 2021-03-07T02:41:03.193600Z

Unison's pretty early on but as all code is immutable and more importantly, all names are transient, you get that extra layer of indirection that symbols/vars give in Clojure. See https://www.unisonweb.org/docs/codebase-organization#upgrading-libraries-to-the-latest-version

quadron 2021-03-07T02:53:19.193900Z

so if different versions of the "same" lib are present, does the importer include the version explicitly where the import is declared?

quadron 2021-03-07T02:59:15.194100Z

how does a namespace know which version it is importing?

emccue 2021-03-07T03:01:19.195100Z

Is it correct to say that in transit, if a json map is produced as output then it must be produced via json-verbose and there won't be any caching

emccue 2021-03-07T03:04:11.195900Z

i'm trying to write a transit library for elm (just doing the decoding side for now)

em 2021-03-07T03:45:45.196100Z

Code is always referenced by hash, so since it's completely immutable, nothing is ever updated. A new "same" version of a lib needs to be explicitly updated by a "patch". Think of it exactly like Git - when someone "updates" the dev branch with code, or when the update the library version, you have to merge the changes. If those merges are like cases in git where you have strictly additive changes (like Rich talked about in non-breaking changes) then you have nothing to do. If any definitions are at all changed, you need to merge those locally. Unison has an interesting mechanism for always making such "patches" or diffs visible, so you always have the granular power to update functions you want to. In fact there's not really a concept of a library, just a bunch of functions that might depend on one another as part of a big group.

em 2021-03-07T03:54:59.196300Z

It's an interesting idea overall, but I don't think it applies to Clojure very well, as I agree with Rich in general that code doesn't need the same immutability properties. I think you get a lot of leverage out of the idea if you build an entire system around it like Unison (free distributed routines, zero compile ecosystems, no "broken" code ever), but a lot of what makes that work is the Haskell-level type system and needing to rebuild the entire thing from the ground. I'm curious how many of the ideas could be somewhat imported into Clojure though

1
Ben Sless 2021-03-07T07:29:12.197400Z

Is there a way to define a "mutually defined" :inline implementation for a function? Something like

(defn foo
  {:inline
   (fn [code]
     (if (pred code)
       `(inline-form ~@code)
       `(foo ~@code)))}
  [& args] (,,,))

pez 2021-03-07T07:43:08.198800Z

I just noticed that when constructing a string from a keyword the string is not identical to its literal form. Anyone knows why this is so?

(= "foo" "foo")                ; => true
  (identical? "foo" "foo")       ; => true
  (= (str 'foo) "foo")           ; => true
  (identical? (str 'foo) "foo")  ; => true
  (= (str :foo) ":foo")          ; => true
  (identical? (str :foo) ":foo") ; => false

Ben Sless 2021-03-07T07:44:24.199700Z

Keyword's toString() instances a new string, while ":foo" is statically initialized in the context of your code

Ben Sless 2021-03-07T07:44:59.200600Z

Unless you use some JVM option to force it to remove duplicate strings they won't be identical

pez 2021-03-07T07:45:46.201100Z

Ah, thanks. But for a symbol it is not like that, I take it?

Ben Sless 2021-03-07T07:46:53.202500Z

Beware of context

(def sym 'foo)
(def s "foo")
(identical? (str sym) s);; => false

❤️ 1
pez 2021-03-07T07:47:19.203100Z

Also, can I safely instruct others that keywords are always identical, no matter how they come to be?

Ben Sless 2021-03-07T08:00:38.206800Z

If you're writing about map keys consider adding the following piece of data: collections as map keys are nice but their performance is hot garbage because lookups always entail an equality check

Ben Sless 2021-03-07T08:01:37.207Z

With less hyperbole

pez 2021-03-07T08:02:09.207200Z

Haha, I was about to ask about that, actually. Thanks!

Ben Sless 2021-03-07T08:04:17.207400Z

Nested maps or synthetic keys have better performance

2021-03-07T15:12:49.223100Z

Even collections as keys can be fast, if your application is set up so that it happens to always do lookups with identical collections, since then clojure.core/= finishes quickly after checking identical?

2021-03-07T15:13:12.223300Z

But that is not necessarily a typical case, I agree. I have seen it sometimes be the case, for some applications.

2021-03-07T15:14:06.223500Z

e.g. a library given some set of values (that might be collections) that it then internally uses as keys in a map to associate data with each of those values. The library will never construct a fresh value of those types -- it will always use the values provided by the caller.

Ben Sless 2021-03-07T07:47:42.203700Z

they were probably only identical because you initialized (symbol "foo") and "foo" in the same context

Ben Sless 2021-03-07T07:47:50.203800Z

yes

Ben Sless 2021-03-07T07:47:56.204Z

because keywords are interned

pez 2021-03-07T07:48:29.204200Z

I thought strings where too, and was writing in a beginners guide that they are, then tested it some…

pez 2021-03-07T07:51:59.204400Z

While I have your attention then, the reason this throws is that map lookup is based on equality?

{(str :foo) :bar
 ":foo" :baz}

Ben Sless 2021-03-07T07:53:16.204900Z

Yes, but it probably throws on the hash collision even before testing for equality

pez 2021-03-07T07:54:50.205700Z

duh! Thanks!

Ben Sless 2021-03-07T07:55:03.205900Z

Strings are usually interned but you have no guarantee they'll be the same object if not initialized statically. Especially with dynamically created strings you wouldn't want to intern them. Keywords are always interned and guarantee identity, which is why they make excellent map keys

Ben Sless 2021-03-07T07:56:16.206200Z

When in doubt I just read the source

pez 2021-03-07T07:57:57.206400Z

It was this suitability for map keys that I was writing about.

pez 2021-03-07T07:59:03.206600Z

Then I went on a tangent with how strings can be lookup efficient too, but I have scrapped that now, since it was a bit deeper than suites the context.

pez 2021-03-07T10:39:27.210100Z

The line between special forms and macros just became blurry to me. I see that the defmacro of let has metadata saying :special-form true . What is the implication of this? let is listed as a special form here as well: https://clojure.org/reference/special_forms#let

kwrooijen 2021-03-07T10:57:12.210500Z

Do we have a channel to ask about Clojure internal implementation?

kwrooijen 2021-03-07T10:57:27.210900Z

For example how namespaces work on the compiler level

kwrooijen 2021-03-07T10:58:00.211600Z

I've been reading clojure core but I still have questions

schmee 2021-03-07T11:00:19.211900Z

@kevin.van.rooijen yes, #clojure-dev is the place

kwrooijen 2021-03-07T11:00:48.212100Z

Thanks!

borkdude 2021-03-07T11:15:33.212500Z

@kevin.van.rooijen https://ask.clojure.org/ is also a good place

borkdude 2021-03-07T11:15:55.213Z

I think #clojure-dev is more for development of clojure itself

borkdude 2021-03-07T11:16:25.213500Z

so this #clojure channel would probably be appropriate for asking questions about clojure internals as well

👍 2
Ben Sless 2021-03-07T11:43:36.214700Z

Anyone here ever extended a function via metadata with Component's lifecycle protocol?

vemv 2021-03-07T13:04:47.215700Z

Probably you mean implementing Component's lifecycle protocol via metadata? Yes, it works well

borkdude 2021-03-07T13:26:36.216400Z

This just crossed my mind: since clojure.spec.alpha is a transitive dependency, anyone using it should probably declare this as an explicit dependency.

borkdude 2021-03-09T11:24:36.348500Z

Btw, are you suggesting that there will be a migration thing in clojure 1.next that will allow you to use clojure.spec.alpha as is, and it will stay this way in future versions?

alexmiller 2021-03-07T13:48:35.216800Z

That seems wrong

alexmiller 2021-03-07T13:49:11.217300Z

You should consider it part of Clojure

alexmiller 2021-03-07T13:49:32.218100Z

Even though it’s broken into a library from an impl point of view

yuhan 2021-03-07T13:51:35.218300Z

I believe this is an implementation specific thing - let can be thought of as a special form, but in practice it's defined as a macro in terms of the actual special form let*

borkdude 2021-03-07T13:56:54.218600Z

Can we rely on clojure.spec.alpha being there always from now on? If not, then declaring the dependency is probably a good idea for future proofing?

p-himik 2021-03-07T14:04:33.219100Z

Makes sense to me, but it doesn't sound like an entry-level comment. let is a special form, that's it. The fact that it's implemented as a macro is an implementation detail that regular users should not care about, let alone beginners.

Ben Sless 2021-03-07T14:09:07.219300Z

I mean implementing it for a function. I want to return a function which will depend on a closed over stateful component. I ended up going the other way and implemented IFn on the record

pez 2021-03-07T14:10:58.219500Z

True, true. But it might be confusing to read about it in a guide where you are instructed to examine all things and find that defmacro. I actually had let among the macros in the guide first, because that’s what I thought it was.

pez 2021-03-07T14:11:45.219700Z

Tricky to strike the balance. The guide promises to be basic in both the conflicting meanings of the word (short, yet foundational).

vemv 2021-03-07T14:11:54.219900Z

What should the function do?

pez 2021-03-07T14:20:14.220100Z

I solved it by not leading with that non-beginner material, and by not explaining it so much when I do mention it: https://github.com/BetterThanTomorrow/dram/blob/56d259ab6af50ca12aa7f3aff5ceca252b61ab52/drams/hello_clojure.clj#L468

Ben Sless 2021-03-07T14:22:48.220400Z

It's the handler passed to a web server

Ben Sless 2021-03-07T14:23:40.220600Z

somewhere in the handler I have an interceptor with a kafka producer

vemv 2021-03-07T14:28:34.221Z

I have a pretty hard time understanding what you're trying to do. It would help to state your original problem, plus your current attempt and its perceived drawbacks

p-himik 2021-03-07T14:29:55.221200Z

I think it looks better. But I'd still mentioned that it's a special form (right now it says "is a form"). Also, a typo at https://github.com/BetterThanTomorrow/dram/blob/56d259ab6af50ca12aa7f3aff5ceca252b61ab52/drams/hello_clojure.clj#L505

alexmiller 2021-03-07T14:31:27.221500Z

The intent was that spec is part of clojure and you don't need to depend on it explicitly

alexmiller 2021-03-07T14:33:47.221700Z

whatever migration we do for spec 2 will depend on that assumption

pez 2021-03-07T14:34:20.221900Z

Thanks. It is part of a section labeled special forms, but I changed it anyway. Staring at where I have a typo without seeing it.

pez 2021-03-07T14:34:48.222100Z

Now I see it! I should rest from this a bit now…

😄 1
Ben Sless 2021-03-07T14:34:49.222300Z

I have a web server where I want to produce incoming messages to Kafka. I wanted to experiment with interceptors a bit so I used Reitit and Sieppari. I handled the production to kafka by instantiating an interceptor which takes the producer as an argument. The interceptor is instantiated when creating the router, so the function which instances a router needs to take the producer as an argument The implementation which emerges from this is two components, one for the web server, one for the producer, where inside the web server I call (http/start-server (routes producer) options) I wanted to create an implementation where the server doesn't directly depend on the producer, only on the handler

👍 1
vemv 2021-03-07T14:48:01.222700Z

> The interceptor is instantiated when creating the router, so the function which instances a router needs to take the producer as an argument This part might be the odd one. Creating routes shouldn't have to pull much detailed knowledge about Kafka concerns. Wouldn't it be possible to create an interceptor in the producer component definition, so that the web component simply depends on producer and pulls the already-done work of creating an interceptor?

borkdude 2021-03-07T14:55:19.222900Z

sounds good, thanks

Ben Sless 2021-03-07T15:49:30.224800Z

And then the function which instances the routes will take the interceptor as an argument?

Ben Sless 2021-03-07T15:50:12.225400Z

Still seems a bit arbitrary. Why just one interceptor as an argument? what about when the API expands in the future?

Yehonathan Sharvit 2021-03-07T15:56:54.227800Z

Is there a way to make byte arrays comparable by value, in the context of of a unit test. For example, I’d like to write a midje prerequesite with a byte array argument, something like this:

(fact … (provided (foo (.getBytes "abc")) => nil)
The problem is that two byte arrays generated from the same string are not equal in Java (for good reasons, because a byte array is mutable)
(.equals (.getBytes "aaa")
         (.getBytes "aaa")) ; => false

borkdude 2021-03-07T15:59:23.228500Z

(java.util.Arrays/equals
 (.getBytes "aaa")
 (.getBytes "aaa"))
=> true

Yehonathan Sharvit 2021-03-07T15:59:58.228700Z

I know

borkdude 2021-03-07T16:00:24.229300Z

(= (vec (.getBytes "aaa"))
   (vec (.getBytes "aaa")))
=> true

Yehonathan Sharvit 2021-03-07T16:00:29.229500Z

My question is: how do I tell = to use java.util.Arrays/equals instead of .equals ?

borkdude 2021-03-07T16:01:13.230500Z

you cannot "tell" =, it's not a protocol (and even if it was, you should not override it for types you don't own)

Yehonathan Sharvit 2021-03-07T16:01:38.230800Z

So what can I do in the context of unit tests?

2021-03-08T14:19:57.250900Z

you can write unit tests that don't use =, I do it frequently

Yehonathan Sharvit 2021-03-08T14:23:14.251300Z

I need a way to convince a midje prerequesite

borkdude 2021-03-07T16:01:44.231Z

write your own =

Yehonathan Sharvit 2021-03-07T16:02:28.231700Z

The problem is that I don’t know how to progragate my definition of = to midje

borkdude 2021-03-07T16:02:39.231900Z

Then don't use midje =)

💯 1
➕ 2
Yehonathan Sharvit 2021-03-07T16:03:52.232900Z

I was hoping that = would call to a method from a protocol that I could override in the context of the unit tests

borkdude 2021-03-07T16:03:54.233100Z

I'm half kidding, but you can't state a "fact" using some wrapper function?

Yehonathan Sharvit 2021-03-07T16:04:11.233400Z

A fact, yet. But not a prerequisite

borkdude 2021-03-07T16:04:21.233600Z

Ask in #midje

Yehonathan Sharvit 2021-03-07T16:04:27.233800Z

Yeah

Yehonathan Sharvit 2021-03-07T16:04:29.234Z

I’ll do

vemv 2021-03-07T16:15:15.234200Z

The interceptor wouldn't be an argument but an extra k-v in this Component systems inject component dependencies into one another via using. See ExampleComponent in https://github.com/stuartsierra/component/blob/9f9653d1d95644e3c30beadf8c8811f86758ea23/README.md , where database is used / depended-on but never passed explicitly. Translating that defrecord pattern to vanilla defns, the result would be:

(defn start-routes [{{producer-interceptor :interceptor} :producer
                     :as this}]
  (assoc this :routes (make-routes producer-interceptor ...)))

vemv 2021-03-07T16:16:18.234600Z

So, adding more 'external' interceptors means simply pulling more data from the destructuring part, which avoids having to add more positional arguments.

rgm 2021-03-07T17:53:58.236700Z

Can anyone point me to a connection pool library like https://github.com/mperham/connection_pool in Ruby? Seems a very Clojure-y concept to have a lib that splits this concern out from eg. hikari. Or does everyone just roll their own?

flowthing 2021-03-07T17:55:38.236900Z

Isn't HikariCP specifically a connection pool library?

flowthing 2021-03-07T17:56:32.237100Z

That's what I've always used, and it's always Just Worked™.

rgm 2021-03-07T17:56:43.237300Z

Maybe I’m reading wrong … isn’t it a JDBC connection pool lib?

p-himik 2021-03-07T17:56:50.237500Z

It is.

flowthing 2021-03-07T17:56:58.237700Z

Oh, yes it is.

rgm 2021-03-07T17:57:14.237900Z

My use case is for keeping 10-20 TCP connections to Apple’s notification service going.

flowthing 2021-03-07T17:57:15.238100Z

I didn't realize you were talking about connection pooling in a more generic sense.

p-himik 2021-03-07T18:00:53.238600Z

So many JDBC connection pools around - perhaps it would be easier to create a JDBC-compliant interface for TCP connections. :D

seancorfield 2021-03-07T18:02:06.238800Z

A quick search does not turn up any TCP connection pooling libraries for Java...

rgm 2021-03-07T18:02:08.239Z

haha, maybe. I’m just a little surprised since it’s almost difficult to avoid using the word “decomplected” to describe what I’m after 🙂

schmee 2021-03-07T18:07:13.239200Z

there are generic object pools like https://commons.apache.org/proper/commons-pool/, I haven’t used them so can’t say if they are any good

🙏 1
flowthing 2021-03-07T18:09:13.239400Z

That Ruby library doesn't seem like a whole lot of code — maybe just port that to Clojure? 😛

rgm 2021-03-07T18:12:46.240100Z

Hah it might come to that.

emccue 2021-03-07T19:10:07.240700Z

example of using the generic object pool

rgm 2021-03-07T19:15:48.241300Z

good example!

rgm 2021-03-07T19:16:43.241500Z

can’t promise a Clojure wrapper lib but I suspect a reasonable interop example is within my abilities.

2021-03-07T19:59:04.241700Z

How would pooling work for "plain" TCP? Wouldn't you have to do a bunch of programming just to get the pooling library to understand your protocol? How would it know whether a connection can safely be returned and used by another thread (i.e. it's not in the middle of framing a message or waiting on a server response)?

rgm 2021-03-07T20:02:20.242Z

Yeah, looking at the ruby one it’s relying on every “connection” being an object so presumably in Clojure terms each “connection” would probably also have to be an object conforming to some sort of lifecycle protocol, with the actual connection buried deep in there someplace.

rgm 2021-03-07T20:02:58.242200Z

something’s got to be doing the bookkeeping.

p-himik 2021-03-07T22:17:45.246700Z

taoensso.carmine.commands has this code:

(defonce ^:private command-spec
  (if-let [edn (enc/slurp-resource "taoensso/carmine/commands.edn")]
    (try
      (enc/read-edn edn)
      (catch Exception e
        (throw (ex-info "Failed to read Carmine commands edn" {} e))))
    (throw (ex-info "Failed to find Carmine commands edn" {}))))
If I start a REPL and run
(require '[taoensso.encore :as enc])
(enc/slurp-resource "taoensso/carmine/commands.edn")
I get a proper EDN. Not a nil. But when I run (require 'taoensso.carmine.commands), I get Failed to find Carmine commands edn. And it only happens on Heroku. I cannot reproduce it at all locally. How is this possible?

p-himik 2021-03-07T22:21:29.248800Z

Of course. Despite all the thinking and staring at the above text for a good minute before posting the message, only after hitting Enter did I realize that I'm compiling Clojure code on Heroku, along with some of the libraries.

p-himik 2021-03-07T22:48:33.248900Z

Seems to work without compilation. But frankly, I still have no idea why io/resource wouldn't work after compilation. Strange.

p-himik 2021-03-07T22:53:42.249100Z

Aaaand now I'm getting "App boot timeout" errors without the compilation. Sigh.

2021-03-07T22:54:40.249300Z

Are you perhaps neglecting to deploy to Heroku the directory taoensso/carmine containing a file commands.edn?

2021-03-07T22:55:20.249500Z

I guess you probably are deploying such a file, if a remote REPL returns a good result for (enc/slurp-resource "taoensso/carmine/commands.edn")

p-himik 2021-03-07T22:55:42.249700Z

Yep, it's all there.