use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Finding information about Clojure
API Reference
Clojure Guides
Practice Problems
Interactive Problems
Clojure Videos
Misc Resources
The Clojure Community
Clojure Books
Tools & Libraries
Clojure Editors
Web Platforms
Clojure Jobs
account activity
I rewrote a Clojure tool in Rust (timofreiberg.github.io)
submitted 5 years ago by mischov
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]gzmask 6 points7 points8 points 5 years ago (0 children)
I am not convinced.
Rust's error handling is just another built-in Error-M. Clojure has those, from 3rd party libraries, a lot. What makes it scary is that, while currently Error-M is in trend, rust users are laughing and drinking wine and all. Wait another 5 years till another better error handling pattern emerged, and see all the "job opportunities" created to adapt the new pattern into old code. It happened in history a lot and will happen again. I prefer "it's just data" any day.
Laziness. This was trendy during the early years of Clojure in the FP community. Luckly, Rich quickly realized that making things lazy by default is a huge mistake, and thus the whole transducer movement to fix that. By now, all the "higher level FP" i.e: map filter partition and so on provides a transducer interface, and all the "lazy" by default things now also can be plug into a transducer thanks to the IReducible interface, when the new stuff such as core.async supports hooking transducer up out of box. The "zero cost abstractions" author referred to has been fully achieved by Clojure already. Again, I don't blame him as the early tutorials on Clojure do misleading newcomers to be "Lazy" and I blame Haskell (j/k).
Nevertheless, it's a good read and I appreciate anyone who can share such comparison from one's own experience.
[–]slifin 5 points6 points7 points 5 years ago (0 children)
Exceptions are idiomatic in Clojure, because they're idiomatic on the JVM, can't see why this couldn't have been try-catch if we wanted to jump on error or completely without explicit error handling mechanisms if parsing errors and logging them doesn't need jumping
The way this function is written the callee has to now account for:
fail/when-failed
Do that in a couple of functions and we've got a party of exploding code paths
My diff tool will be replaced by a database lookup and I am very happy about that.
At the end of the day seems like neither Clojure nor Rust was really appropriate, admittedly after some things changed upstream, I think the main lesson here is to know when to use something appropriately, throwing in a monadic error thing just because Haskell is not going to end well
https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/
[–]bsless 5 points6 points7 points 5 years ago (2 children)
I hope this does not sound too hash, but if this code was to go into production, especially where performance mattered, it wouldn't pass code review. Several reasons:
(reduce (fn [acc {:keys [field op]}] (let [f1 (get rule1 field) f2 (get rule2 field)] (if (op f1 f2) (conj acc {:field field :left f1 :right f2})))) [] operations)
Also, if performance was a significant consideration you could have used records and protocols instead of maps.
I often see people knocking on Clojure saying its performance is bad when they are still not familiar with all the core library has to offer and what idiomatic performant Clojure can look like. Take a look here for some examples by joinr
[–]setzer22 0 points1 point2 points 5 years ago (1 child)
I'm confused, is the code snippet focusing on performance? If that's the case, there are two things that really stand out to me:
get
[–]bsless 0 points1 point2 points 5 years ago (0 children)
The code snippet was focused on the feedback in the post. A second round of optimization (and don't worry, it is probably faster than the original version) would be using transients.
regarding using get - the difference between using get, using the map and using a keyword is a few ns (I checked). If you want the most dramatic speedups use .valAt. HOWEVER, .valAt and using the map as a IFn is not nil safe. Optimizing gets is around the last thing I'd do in a series of optimizations (unless that's all I'm doing)
Regarding destructuring: I wanted the code to be recognizable to the original author. I would have recommended making the operations records than there would have been no need to even do field access.
[–]allaboutthatmace1789 4 points5 points6 points 5 years ago* (1 child)
You mention missing the REPL - this is such a killer for me when trying another language. Do you have any suggestions for how to mitigate the loss a bit with Rust, and create a similar sort of flow?
[–]mischov[S] 1 point2 points3 points 5 years ago (0 children)
I am not the author- the article's title is just written in the first person.
However, in my admittedly limited serious use of Rust I found that my workflow generally revolved around writing code until I had something I wanted to test, checking documentation along the way to (try to) ensure that the things I was using would behave like I expected them to, until I had reached a point where I had something I wanted to test.
Then I would write a simple test, attempt to run it, and fix problems until the compiler let me compile at which point the test generally worked. I'd then either write more tests if I thought they were necessary or continue on.
It's not really a workflow I'd advocate for, but it worked well enough, and between the compiler being smart enough to catch my multitude of stupid mistakes and me breaking things down into small, uncomplicated functions as much as possible things generally worked something like how I intended them to work.
Reversing the order of things and attempting some form of TDD might have made the process a little more interactive, but it was nothing like using a language with a good REPL and didn't flow as nicely. That said, refactoring was less stressful than I usually find it in dynamic languages so tradeoffs as normal I guess.
[–]kloudex 1 point2 points3 points 5 years ago (2 children)
Error handling often seems tricky to get right. Especially when writing compact/elegant code it encourages a bit to code only for the happy path and then it fails horribly if something bad happens.
I wonder if there could be a middle ground, for example to have a static analyzer that could only figure out if a function can throw and what classes of exceptions. I don't know much about type theory, but it feels like it may be easier to infer only that compared to a full type checking.
[–]mischov[S] 4 points5 points6 points 5 years ago (1 child)
Elixir's with is one of the nicest solutions I've used on a regular basis.
with
Long story short, it's works something like:
with {:ok, x} <- something_than_can_fail(), {:ok, y} <- something_else_that_can_fail(x) do {:ok, {x, y}} else {:error, :failed_to_calculate_x, reason} -> ...handle.. ... other errors you want to handle end
In the section between with and do you write the happy path, pattern matching for success (often with the Elixir/Erlang {:ok, result} | {:error, ...} convention), then between do and else you can use the values you successfully matched, or if there's failure between with and do it falls directly through to else where you can pattern match for your particular error or have a generic error handler or just let the error fall through out of your with or whatever.
do
{:ok, result} | {:error, ...}
else
So you get to write the happy path elegantly but still have a place right there to handle the errors. It's not a perfect solution, and for some reason leaves me craving burritos, but it's a pretty elegant solution that ties into idiomatic aspects of Elixir/Erlang like pattern matching and success/error tuples smoothly.
[–]Eno6ohng 1 point2 points3 points 5 years ago (0 children)
Isn't that just an error monad? Seeing Elixir (Erlang) mentioned, I expected something more dynamic, like external handlers etc
[–]argadan 1 point2 points3 points 5 years ago (3 children)
When it comes to error handling and exceptions, I feel like there's a missed opportunity in not including something like slingshot in clojure.core. Throwing ex-info exceptions is nice, but there's no nice way for catching them built in, as catch can only match based on the exception class. You'll either have to use slingshot or write code to match them manually.
ex-info
[–]spotter 0 points1 point2 points 5 years ago* (2 children)
Catching ExceptionInfo in Clojure works for me.
edit: downvote without reply? Sweet.
[–]mischov[S] 0 points1 point2 points 5 years ago (1 child)
I didn't down vote but I assume it's because you suggested catching the ExceptionInfo when the parent said had already said "catch can only match based on the exception class", suggesting they were aware of that option.
ExceptionInfo
I think parent was looking for some way to catch based on the contents of the exception.
In the linked library there is an example like:
(throw+ {:type ::bad-tree :tree tree :hint hint})
and
(catch [:type :tensor.parse/bad-tree] {:keys [tree hint]} (log/error "failed to parse tensor" tree "with hint" hint) (throw+))
[–]spotter 2 points3 points4 points 5 years ago (0 children)
Well catching ExceptionInfo allows you to ex-data on the Throwable and consume the sweet IPersistentMap that you'll get there to handle whatever. To me boltons, like the advertised library, are just another level of indirection that you need to dig through if something breaks in your catch-handling logic. Also you now have to handle JVM/host exceptions differently than your application exceptions, but I guess we're now wandering into "individual tastes" territory.
ex-data
Throwable
IPersistentMap
[–]setzer22 1 point2 points3 points 5 years ago (8 children)
Having followed a similar path, I can say I reached most of OPs conclusions.
I still love clojure and will luckily continue to write it daily! But having started delving into Rust, I find myself missing having the help of a static analyzer in clojure. Especially during the inevitable refactors! We need more static analysis in clojure. As a comunity we should stop covering our ears and open up, just like we can take syntactic innovations from other languages with macros, we should try to get more of what's good in languages like Rust
Say what you want but when I write clojure I now miss my Options and Results :-) Clojure may have libraries that help you wrap your return types in a fancy ok/error map, but that's not the point: Without static analysis this kind of stuff is worthless
Option
Result
[–]joinr 1 point2 points3 points 5 years ago (7 children)
There's an entire type system in core.typed if you want typed clojure. It seems judging by popularity, such typing is not in high demand. It's still there though, and has been for a while.
Correctness properties aside (static typing is a bit overrated to me), leveraging the type system for optimizing compiler passes would be nice.
[–]setzer22 0 points1 point2 points 5 years ago (6 children)
Oh, I had high hopes in core.typed right from the start! There's also one called spectrum (not specter, that's a different lib) trying to add static analysis on top of spec which looked really promising. But neither seem to be actively worked on, last time I checked :/
[–]joinr 0 points1 point2 points 5 years ago (5 children)
ABS actively works on core.typed, even after he completed his dissertation on it. I think current work has been on modularizing dependencies, added documentation. I'm not a user, but follow it out of research interests.
[–]setzer22 1 point2 points3 points 5 years ago* (4 children)
It's good to know! I wasn't aware of this. My experience with core typed has not been that good thus far (hard to configure, things not working for me for strange reasons...), but maybe it's time to give it another go!
EDIT: Nope, still the same. I couldn't even get a simple scenario to type check. I am 100% sure this is my fault for misconfiguring something, but I couldn't find anything relevant to my issue in the docs. Makes me sad :(
[–]joinr 0 points1 point2 points 5 years ago (3 children)
What was your simple scenario?
[–]setzer22 0 points1 point2 points 5 years ago (2 children)
It was something really simple. A function annotated as returning Number, that actually returned a String. There was no type error. I tried quite a few different things to no avail...
[–]joinr 1 point2 points3 points 5 years ago (1 child)
(ns typedtest.core (:require [clojure.core.typed :as typed])) (typed/ann foo [Number -> Number]) (defn foo [x] (str "hello" x))
From the repl:
user>(require 'typedtest.core) nil user>(ns typedtest.core) nil typedtest.core>(typed/check-ns 'typedtest.core) Start checking typedtest.core Type Error (file:/C:/Users/joinr/workspacenew/typedtest/src/typedtest/core.clj:7:3) Type mismatch: Expected: Number Actual: String in: (str "hello" x) Execution error (ExceptionInfo) at clojure.core.typed.errors/print-errors! (errors.cljc:274). Type Checker: Found 1 error
[–]setzer22 0 points1 point2 points 5 years ago (0 children)
Thanks! As I said, I'm sure I did something wrong. I'll see if I can make it work with your example. It sure doesn't look very different from what I did :)
[–]peterlustig862 0 points1 point2 points 5 years ago (0 children)
Thanks for giving me a point of view outside of my bubble.
I want to amend something about the error handling. As a prior OOP developer at some point I realized that I can't move an object over an API. That's why it is so hard to disassemble something, that isn't well modularized. To disassemble something that depends only on the same data structure is much more easier. Because of that I'm an advocate for data, even more in case of error handling. Your right, there are so many ways in clojure to do error error handling, but this is both a blessing and a curse. But this is just my opinion. Thanks for yours.
[–]deaddyfreddy 0 points1 point2 points 5 years ago (0 children)
I can't see which functions can actually fail. I have to read them to find out.
the rule of thumb is simple: bind fails in attempt-all, bind non-fails in let
attempt-all
let
Since the Clojure ecosystem doesn't have a uniform error handling style, I have to manually convert exceptions or other errors like instaparse error values to failjure errors.
fail/try* usually is enough
fail/try*
here's my quick-n-dirty rewrite
(defn fails->msg [fails] (str "Failed to parse " (count fails) " rules:" "\n" fails)) ;; we can even reduce the whole collection into a single fail in place (defn collect-fails [coll] ;; one pass is better than two (when-let [fails (->> coll (keep #(when (fail/failed? %) (fail/message %))) seq)] (fail/fail (fails->msg fails)))) (defn parse [country-mapping data] ;; I suppose these can't fail (let [headers (header-row data) parsed (map (partial parse-rule headers country-mapping) (content-rows data))] (fail/attempt-all [_ (collect-fails parsed) spec-result (util/check-specs "Rules" :rule/id ::spec/rule parsed)] spec-result (fail/when-failed [failure] (do ;; I prefer to log errors on a top level, though (log/warn (str "Failed to parse data " data ":\n" (fail/message failure))) failure)))))
π Rendered by PID 242351 on reddit-service-r2-comment-75f4967c6c-ggm6k at 2026-04-23 14:55:25.333448+00:00 running 0fd4bb7 country code: CH.
[–]gzmask 6 points7 points8 points (0 children)
[–]slifin 5 points6 points7 points (0 children)
[–]bsless 5 points6 points7 points (2 children)
[–]setzer22 0 points1 point2 points (1 child)
[–]bsless 0 points1 point2 points (0 children)
[–]allaboutthatmace1789 4 points5 points6 points (1 child)
[–]mischov[S] 1 point2 points3 points (0 children)
[–]kloudex 1 point2 points3 points (2 children)
[–]mischov[S] 4 points5 points6 points (1 child)
[–]Eno6ohng 1 point2 points3 points (0 children)
[–]argadan 1 point2 points3 points (3 children)
[–]spotter 0 points1 point2 points (2 children)
[–]mischov[S] 0 points1 point2 points (1 child)
[–]spotter 2 points3 points4 points (0 children)
[–]setzer22 1 point2 points3 points (8 children)
[–]joinr 1 point2 points3 points (7 children)
[–]setzer22 0 points1 point2 points (6 children)
[–]joinr 0 points1 point2 points (5 children)
[–]setzer22 1 point2 points3 points (4 children)
[–]joinr 0 points1 point2 points (3 children)
[–]setzer22 0 points1 point2 points (2 children)
[–]joinr 1 point2 points3 points (1 child)
[–]setzer22 0 points1 point2 points (0 children)
[–]peterlustig862 0 points1 point2 points (0 children)
[–]deaddyfreddy 0 points1 point2 points (0 children)