This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
- # announcements (1)
- # babashka (83)
- # beginners (67)
- # chlorine-clover (22)
- # cider (11)
- # circleci (6)
- # clj-kondo (12)
- # cljs-dev (137)
- # cljsrn (15)
- # clojure (124)
- # clojure-europe (40)
- # clojure-italy (1)
- # clojure-nl (3)
- # clojure-norway (1)
- # clojure-serbia (3)
- # clojure-spec (19)
- # clojure-uk (14)
- # clojuredesign-podcast (5)
- # clojurescript (80)
- # conjure (49)
- # core-async (62)
- # cursive (18)
- # datascript (1)
- # datomic (64)
- # docker (28)
- # emacs (20)
- # figwheel-main (247)
- # fulcro (95)
- # graalvm (2)
- # jobs-discuss (11)
- # joker (2)
- # juxt (4)
- # lambdaisland (9)
- # leiningen (1)
- # meander (14)
- # mount (6)
- # off-topic (16)
- # pathom (46)
- # re-frame (35)
- # reagent (6)
- # reitit (5)
- # shadow-cljs (28)
- # spacemacs (6)
- # sql (18)
- # tools-deps (26)
- # vim (8)
- # xtdb (23)
- # yada (1)
Hey everyone! Hope everyone's night is going along nicely. Trying to make sure I'm not building a footcannon and I'm having trouble coming up with a concise search phrase for this. Essentially I'm trying to expose another namespace's
defprotocol functions in a separate "top-level" namespace. The setup is something like:
(ns some.ns.proto) (defprotocol Proto1 (f1 [x]))
I'm pretty happy with the namespace organization, but I'm trying to figure out if I'm asking for bad by redef'ing a
(ns some.ns.api (:require [some.ns.proto :as proto])) (def f1 proto/f1) ; Won't work as expected when `extend-protocol` is called "after" this `def`, presumably because of the `alter-var-root` call in `clojure.core/extend` (def f1 (var proto/f1)) ; Works!
varlike that... Is there a "right" way to do this? Any input would be vastly appreciated.
There's a well-known library called Potemkin that has an
import-vars macro to do this sort of thing but the consensus is usually expressed to avoid it 🙂
Well, the idea was to have the protocol available essentially in isolation, a given implementation available in another namespace (also in isolation), and then have a "batteries included" namespace at the top, that requires the protocol AND the "default" implementation, without consumers needing to
require two different namespaces to work with /all/ extended objects.
If you look at most libraries that have protocols, they tend to have their protocols in a single namespace and the expectation is that clients should explicitly require that namespace in order to access those protocols.
Right, right. Well it's certainly easy enough to require the
protocol namespace in consuming namespaces. I sure am glad I asked 😂
The other thing to consider is that it is common to write wrappers for protocols -- and expose those in the top-level API namespace.
The primary API is wrappers in
next.jdbc. The protocols are in
next.jdbc.protocols and most clients do not need to require that.
next.jdbc.result-set etc have standard implementations of those protocols (which are required into the top-level API namespace, but can be directly required into client code when access is needed to something specialized).
The other benefit for writing wrappers is that you cannot instrument protocols but you can instrument wrappers 🙂
Out of curiosity, if someone were to extend those protocols "downstream", would the wrappers work just as well on /those/ extensions? It seems that is the case I was running into
If you need to extend the protocols you must require the namespace that defines the protocols -- and so you should: you are explicitly opening up the protocol namespace and adding things.
Right. Well excellent! Ok this has become fascinating—last question, I promise (thank you so much for your time tonight Sean!). The following does exactly what I was hoping it would—does this approach trigger the same knee jerk, or is this a better "wrapper":
(ns some.ns.api ,,,) (def f1 (fn [x] (proto/f1 x)) ;; or any equiv, e.g. (defn ,,,), (partial ,,,) etc.
(mostly it's clearer to use a
-name for the protocol functions so that folks cannot confuse the wrapper for the protocol)
That's particularly important when you write multiple arity wrappers (like
Oh man, this is so helpful. Makes perfect sense. That fully settles it, then. Apologies for the poor framing there toward the end, but your answer totally covered it.
next.jdbc has direct (same-named) wrappers for
prepare and then has multi-arity wrappers for
-transact. The latter has a macro wrapper as well.
(and probably also worth noting that the default implementations are split across three namespaces:
next.jdbc.transaction 🙂 )
Is there any consideration with naming protocol functions with a leading
- in java interop? I feel like I've seen blog posts where some java interop expected/required
-xyz, or something. But maybe that was just the conventional/context-specific rather than a real syntactical requirement
- is the default prefix for (Clojure) functions that are exposed as Java methods.
That tends to spill over into protocols where folks will often use
--prefixed names for the protocol functions and unprefixed names for the wrappers -- but that's purely a "common convention". I've seen other conventions... and some fairly random mismatches between wrappers and protocol functions.
gen-class was totally what it was. Got it. You have tremendously impacted my course through
protocolspace tonight Sean. Thanks again!!!
Ah, yes. I think I'm seeing where this all went astray. I think I can pretty easily work around this without the dirty
var redef while keeping everything relatively clean. Can't tell you how appreciative I am of the help @seancorfield!!!
This thread has some good discussion about it @neo2551 [thread link removed] this message https://groups.google.com/d/msg/clojure/Ng_GXYSrKmI/MJsKDKhJCQAJ in particular
Also https://groups.google.com/d/msg/clojure/-PvQvVmmLQg/0CmSJfTsFAAJ has some cautions about Potemkin
Also from that first thread https://groups.google.com/d/msg/clojure/Ng_GXYSrKmI/ULKD5cN9BAAJ (Alex Miller cautioning against
To me, use of Potemkin indicates either poor or lazy API design (for several of the reason mentions in those threads).
Great reads! Didn't know those aspects.
From intuition, I never used Potemkin
i-v as a usual weapon, but it seemed like a good occasional resource. Will correct that conception myself.
As an additional data point, using
import-vars will create an annoying https://github.com/jonase/eastwood linter complaint (which is correct in itself, but tedious to address)
I have used a similar technique in a very specialized, limited context: creating
expectations.clojure.test which is a
clojure.test-compatible version of the classic Expectations library and, for convenience, you can just require
expectations.clojure.test instead of
clojure.test and still have access to a handful of API functions that are identical -- and "imported" from
But I definitely do not advise it as a general way to organize/build APIs. And this is very specific in how it deals with metadata, in order to work better in editors/IDEs.
What were the reasons you switched to clojure? I am trying to convince my OOP colleagues to switch into FP programming but even when they see how cool clojure is using repl they still stay with OOP. So how would you summarize benefits to using clojure instead of other OOP languages.
• Interactive Development style • REPL interaction with other JVM processes (in fact, I established that idea using Groovy before) • Simplified DSL development • Compactness of code • Superior immutable data structures • Absolutely reproducable and testable behaviour (for all pure parts) • Simple code generation • multimethod dispatch magic • getting rid of superfluous bloaty framework magic Just to name a few of the very pragmatic advantages... Summary: Iterate faster, improve maintainability, have more fun.
Benefits are the same as having a freshly sharpened chef knife over a dull one. You can do all the same things, but it's way more satisfying to do so when you're knife is really sharp and slices through things easy, painfully and quickly
To add to what others have said: - Clojure has a really nice polymorphism model. It's much more robust and extensible than others I've seen. Although it might not be apparent at first sight, especially to someone coming from the dreaded OOP world - "Code is data" goes a long way - It's much easier (and simpler) to compose functions than classes
Also, the ecosystem is really nice. Tools, libraries, community. Libraries is my pet peeve with other languages. So much garbage around that managed to become popular, it's mind boggling.
I'm not sure how easily you can rationally convince most people to change. The most compelling reason for me personally is that it made my job less painful, less boring, and more fun. But a lot of people are really reluctant to take the temporary hit and feel less capable by switching away from languages and paradigms in which they've become comfortable.
Things I wrote a couple of years ago: https://medium.com/appsflyer/why-clojure-a52d033769a8 https://medium.com/appsflyer/why-clojure-part-2-aace98151ce2
@U07VBK5CJ Very good point! These decisions are often less about rational reasons and more about excitement and joy. So inspiring curiosity and sharing Rich Hickey talks or other cultural community artifacts (live coding overtone?) with your people might be a better approach for getting buy-in of developers. Convincing Management is a different issue, though.
@UC0SN2H0E Folks who are deeply rooted in OOP thinking, especially if it's the only paradigm they've ever used, often find it really hard to get into FP. It forces them to "unlearn" a lot of what has become second nature to them. This can make it really hard to convince them that FP is "better" because, right now, they're competent and productive and any attempts they make at FP will be alien to them and even the simplest code will take them much longer to write.
In addition, if they're used to a statically typed OOP language, they'll probably find Clojure's "lack of types" to be very disorienting.
At work, we cross-trained two developers from OOP backgrounds to Clojure. After a year or so, one left to return to the same OOP language he was used to because, although he enjoyed Clojure, he never really felt comfortable with it. The other developer eventually left for personal reasons but has gone on to other full-time Clojure roles at other companies. In other words, not everyone will stick with Clojure even if they try it (and seem to like it).
I would suggest writing your own code in a more FP style within the "home" language for your company and see how your colleagues react to that. If they push back and want your code to remain in a more OO style, that says you're very unlikely to get them to switch. If they like what they from your change of style, you'd probably have a better chance of introducing Clojure.
Going back to your first question: I switched to Clojure because a) I already had a background in FP (from the '80s, before OOP exploded) and b) I'd been slowly moving that direction (my overall arc was C, C++, Java, ColdFusion/CFML, Flex/ActionScript, Groovy, Scala -- and finally to Clojure a decade ago).
@seancorfield "I would suggest writing your own code in a more FP style within the "home"..." Actually this is the path I am trying, slowly introdicung map, filter, reduce, etc.. And your point about lack of types is great too, in my last job where I build FP app (using ramda in js) when I left they switched to typescript and said they could not wrap their heads around ramda style (probably mostly my fault because I was not as good in FP as I should be at that time)
"made my job less painful, less boring, and more fun" - this is it to me. I just find Clojure more fun.
with clojure you can care a lot less about breaking someone else’s class due to some logic change
I must be missing something, but I wish that
update-in did something useful with an empty path, like executing
f against the map.
there is a ticket (closed) for that - https://clojure.atlassian.net/browse/CLJ-373
I'm trying to invoke clojure process from another clojure process.
ATM I'm using
java.util.Process stuff with
["java" "-co" "a clojure cp with clojure jar" "clojure.main"]
It start nicelly but when I try to
(.write p (.getBytes "(prn :hello)")) it writes, evaluates and exit, like
$ echo "(prn :hello)" | clojure
How do I avoid this exit behavior? I'm sure that i'm (almost) not sending a
an end-of-file isn't a special message, it's a condition that's hit when input closes
you likely want something like the socket server that lets you evaluate chunks of text but keep running
also I'm assuming that you are writing to an input stream, not the process itself
also the number of discrepancies makes me think it would probably be better to post more of the actual code :)
Are there any good resources for applying Rich Hickey/Clojure wisdom to writing regular Java code? I'm looking at some java that uses classes to represent domain information, with
synchronized methods for fine-grained mutation of individual data fields, and I'm searching for a path towards a more clojurey solution but I don't quite know where to begin.
What I'm looking for is something akin to a book written by Rich called "Java, The Good Parts"
final fields an immutable instances of Java objects all over the place in the implementation of Clojure, and Java Concurrency in Practice often mentions immutable objects as a way of not having to think harder about whether you have gotten the concurrency aspects correct. That is atypical Java code, of course.
Prefer creating APIs based on interfaces, often very focused ones with perhaps only one method signature in them, or perhaps a few closely related ones that really always belong together, versus implementation inheritance.
Google's Java guides strongly advocate for immutable classes. I have begun to think this is common wisdom now and not atypical Java.
Could be. Rich was strongly recommending reading Java Concurrency in Practice in 2008 (perhaps earlier before he was giving talks that are available on YouTube). I read it last year, and it still seems to me like a good source for that kind of info.
It walks an amazing line of not telling you: "You should always use immutable objects", giving plenty of advice on how to make things safe when you have mutable objects, but the rules for safety are certainly harder to follow, and keep following them over the maintenance lifetime of a program.
@U0CMVHBL2 haha I guess the enlightenment of immutability is stronger when you reach the conclusion yourself 😃
(I have not read it cover to cover yet.) Maybe when it was written, immutable everything still was not practical (too slow).
@UJRDALZA5 also without persistent immutable data structures which Clojure has, yes, it can be a problem, esp with large collections
Yeah, I also think Clojure would not be the way it is if persistent collections were not invented. They form the backbone of Clojure itself.
If there were enough good parts, he wouldn't have written Clojure :)
@ben.grabow I would say… if you really need to write Java, write it as if you’re writing Clojure, wonder if there’s an “easy” way to pull the data structures out of Clojure, and use them from Java
Caveat emptor: bifurcan is cool. I'm not sure it is as battle-tested as Clojure's immutable collections are, yet.
@raspasov What I used to do was annotate every class with @Immutable and then use a static analysis tool like FindBugs.
bifurcan has been extensively tested with generative testing via collection-check so I would expect it to be pretty solid
I might be advising more caution than necessary there. My use of it was only testing its equivalent of core.rrb-vector, and finding neither it nor any other implementation of RRB vectors that I couldn't find bugs in within a day or three of trying.
Once mutation has been moved out of domain objects, is it just a matter of using
compareAndSet to do coarse-grained mutation?
Instead of atomically setting a person's first name, then atomically setting the person's last name (fine grained mutation) you construct a new map with the new values of the first and last name, then atomically swap the reference of the old map for the new map. This concept can be extrapolated until any mutation in your system is an atomic swap of a single, top-level atom. See cljs reagent as an example.
that should happen only where you'd use an
atom in clojure (in other words almost never), most of the time you'd just accept and return immutable values from each method
another thing to note is that building an algorithm to use mutation inside a small scope is often fine, as long as the interface is pure and the scope of the mutable values is narrow enough
eg. - don't mutate things that are passed in / pass mutables to other code, but mutating for intermediate values is often just fine
There are other knock-on issues to tackle as well, which is why I'm looking for something like a comprehensive reference. How do you manage deep equality on nested data structures? How do you represent domain information in a composable way?
Nested immutable values do compose quite well, at least certainly they do in Clojure, and I cannot think of why that would not be the case in any programming language with immutable values, including immutable collections that nest.
Equality is deep equality in such a scenario. There are implementation optimizations you can play, as Clojure takes advantage of, like "if two immutable values being compared are pointer/reference equal, then they are equal, without having to recurse into their components"
Clojure programmers often use maps to represent domain information, nested if that is useful, with vectors in places where order is important.
I believe in and agree with all these things in theory. I suppose what I'm looking for are some examples of this ethos put into practice in Java specifically, in hopes of learning from someone else's hard work to manage connecting the pure, immutable parts with the impure, mutable, idiomatic Java parts.
I don't have examples of Java applications to give you off hand, if that is what you are looking for. Most Clojure applications that use Java libraries are of this kind, with various approaches used at the interface between Clojure code and mutation-heavy Java library.
The answers to these questions generally have to play well together, so it's hard to build a picture of a working system with isolated answers to each question.
I think this discussion just highlights how difficult it is to follow Clojure semantics in a language that doesn't have immutable data structures built-in: there's so much in Java-the-language that keeps leading you to mutation-based code. It's not expression-based, so you're constantly fighting against statements, trying to produce values and avoid the "lure" of side-effects. Basic language structures for iteration all assume mutability: you have to work hard to write computations that avoid that (using streams everywhere, which is kind of ugly at scale).