[HN Gopher] I rewrote a Clojure tool in Rust ___________________________________________________________________ I rewrote a Clojure tool in Rust Author : praveenperera Score : 116 points Date : 2020-12-21 17:23 UTC (5 hours ago) (HTM) web link (timofreiberg.github.io) (TXT) w3m dump (timofreiberg.github.io) | Narishma wrote: | The code snippets in the article are unreadable due to low | contrast. | kimi wrote: | Not sure about Rust, but the on the Clojure side, code does not | seem pretty. | gcb0 wrote: | ah, and the real world meet language flame war. | | both of these codes are awful imo. but real world code always is, | so that's not a valid critic. | | usually when code pass over errors they are just ignoring design | holes and offloading the work to fix the design to the end user, | but that's also how real world programs are architected every | day. | | in the end: great, and bave article, to show how the languages du | jour elegant syntax sugars added with formalism and other | academic dreams in mind will be bent over backwards to just-get- | shit-done in a needlessly complex method that tries to do | everything with whatever data structure the underlying library | (excell in this case) decided to presented the data instead of | the better one for the method logic. | TurboHaskal wrote: | I loved the "mutability here! it's fine though" comment, it reads | a bit like "I'm not racist but...". | | I guess this is what happens when you spend too much time in the | functional programming cult. | omn1 wrote: | Great post! I like articles where the author has practical | experience in two languages and compares them. Helps me make | better decisions for future projects. | [deleted] | SomeHacker44 wrote: | I personally was quite astonished (not in a good way) by the use | of #_"comments" instead of ;[;[;[;]]] comments. | saleheen wrote: | Great Post! | chrisulloa wrote: | While I definitely agree Rust is a much faster language than | Clojure, I would be interested to see benchmarks on your code | that show just how much faster your Rust code was on the same | data. | | I also noticed that you mentioned avoiding lazy sequences is not | idiomatic in Clojure. I disagree with this since using | transducers is still idiomatic. I wonder if you could've noticed | some speed improvements moving your filters/maps to transducers. | Though I doubt this would get you to Rust speeds anyway, it might | just be fast enough. | j-pb wrote: | I just moved a medium sized codebase from clojure transducers | to JS, and after having used clojure for 7+ years, and done so | professionally, I don't wanna go back, ever. The JS solution is | shorter, faster, and easier to understand. I'm thankfull for | the insights into reality and programming clojure has provided, | but highly optimised clojure is neither idiomatic nor pretty, | you end up with eductions everywhere. Combine that with | reaaaallllyy bad debuggability with all those nested inside out | transducer calls (the stack traces have also gotten worse over | the years, I don't know why, and a splintered ecosystem (lein, | boot, clj-tools)) I'd pick rust and deno/js any day for a | greenfield project over clojure. sadly. | kostadin wrote: | I have limited exposure to Clojure transducers but I spend | most of my time writing JS/TS and I've found | thi.ng/transducers[0] a pleasure to work with and super | elegant for constructing data processing workflows. | | [0] https://github.com/thi- | ng/umbrella/tree/develop/packages/tra... | nbardy wrote: | Debugging performance problems is the reason I stopped using | `cljs`. Those stack traces are so painful. | bjoli wrote: | I doubt it will bring much. If properly implemented, there is | nothing that makes generator-like laziness slower than | transducers, and since it is pretty central to clojure I doubt | you will see much speed gain by using transducers. | | In scheme, the srfi-158 based generators are slower than my own | transducer SRFI (srfi-171) only in schemes where set! incurs a | boxing penalty and where the eagerness of transducers means | mutable state can be avoided. | | Now, I know very little clojure, but I doubt they would leave | such a relatively trivial optimization on the table. A step in | a transducer is just a procedure call, which is the same for | trivial generator-based laziness. | lilactown wrote: | When I was reading the article, I thought the author of the | post was probably pointing more in the direction of Clojure's | immutable data being slower, rather than laziness | specifically. | | IME (admittedly in a different context, doing UI development) | Clojure's seqs and other immutable data can be a huge | performance drag due to additional allocations needed. If | you're in a hot loop where you're creating and realizing a | sequence immediately, it's probably much faster to bang on a | transient vector. Same with creating a bunch of immutable | hash maps that you then throw away; better to create a | simpler data structure (e.g. a POJO or Map) which doesn't | handle complicated structural sharing patterns if it's just | going to be thrown away. | | Transducer's would help in the author's first case to take | the map/filter piped through the `->>`, which is going to do | two separate passes and realize two seqs, and combine it into | one. | bjoli wrote: | I stand by what I said (even though I am partial to | transducers): there is no reason for lazy sequences | overhead to be much more than a procedure call, which is | exactly what a step in the reduction in the case of | transducers is. At least when implemented as in clojure or | srfi-171. | | I understand that there might be some overhead to creating | new sequence objects, but removing intermediate sequence | object creation should be simple, at least for built in | map-filter-take-et-al. | | Edit: I seem to be wrong. People are recommending | transducers over clojure's lazy map-filter-take-et-al | because of overhead, which to me seems off, but there might | be something about clojure's lazy sequences that I haven't | grooked. | adamnemecek wrote: | I mean Clojure is still running on JVM so there will be at | least that difference. AFAIK Clojure is slower than Java so | there's that also. | dgb23 wrote: | Transducers are definitely idiomatic. They are more general | over "similar things to transform in steps" (including | sequences, messages and so on), so you can apply them to | collections ("I have the whole data in advance") or channels | ("I get the data piece by piece") and so on. | | Another idiomatic way to improve performance are transients[0]. | From the outside your function is still a function, but on the | inside it's cheating by updating in place instead of using | persistent data structures. See the frequencies function for a | simple example[1]. | | Clojure and Rust are both very expressive languages and even | though they both can be considered niche, they have _massive_ | reach: Clojure taps into JVM and the JS ecosystems, Rust can | also compile to WASM or be integrated with the JVM via JNI. | | The big difference between the two, and why I think they | complement each other nicely, is that Clojure is optimized for | development, and does its best at runtime, but Rust is | optimized for runtime, and tries its best at development. (A | similar take in the article). In other words: they both achieve | their secondary goal well, but resolve trade-offs by adhering | to their primary in the vast majority of cases. | | [0] https://clojure.org/reference/transients | | [1] | https://github.com/clojure/clojure/blob/clojure-1.10.1/src/c... | burnthrow wrote: | > After switching to Rust, I had to implement more complicated | logic that resolved dependencies between the rules I was diffing. | This became complicated enough that even with a static type | system I could only barely understand it. I doubt I would have | been able to finish it in Clojure. | | I have no idea what the author's trying to say here. | mattrepl wrote: | I believe they are saying the static type system and type | checker provided by Rust helped them write complicated code. | burnthrow wrote: | Yes but it's not clear how. The post is light on details in | general. | mistersys wrote: | Sure, but having used static vs. dynamic languages | extensively on gnarly problems, it's pretty clear what | they're saying. There comes a point where dynamic languages | can be quite hard to debug compared to language with well | deigned types, especially when working on tricky problems | with many constructs and elements. Less rules does not | equal greater productivity. | kristoft wrote: | Nice post, very well written! Just not sure why compare non-gc | low-level statically-typed language with vm-based gc dynamic | language. | nindalf wrote: | It's a fair comparison because many developers, including the | author, would have to make a choice between languages, some of | which would be GC and while others are non GC. | didibus wrote: | A bit sad there was no profiling done, or at least the article | doesn't mention it. Maybe optimizing Clojure wouldn't have been | that hard, could have been only a few places needed tweeking. In | any case, Rust is obviously targeting high performance in a way | Clojure isn't. Rust is faster than Java, and Clojure can only | ever match Java in performance, not exceed it. But still, it's | not clear if the author tried to optimize the Clojure version or | not? | dgb23 wrote: | > Rust is faster than ... | | Not if you use the wrong constructs, copy stuff around in the | heap, use ref counting everywhere in longer running processes. | | I'm not nitpicking here, in Rust you can get really fast, but | its on you to make that happen. | | For example persistent data-structures (used in Clojure) are | really fast and for some operations and cases even close to | optimal. | | Performance is hard, and I very much agree with your question | here. What has been measured and what are the results. | didibus wrote: | I agree with you, but I think the assumption here is that | we're comparing two code bases that are both trying to be | performant. The Rust defaults will probably start off more | performant, and the ceiling will be higher as well. | | A lot of the performance comes from the different paradigms | though, so it's not always an apple to apple comparison. But | I also think that's an assumption being made when talking | about a Clojure Vs Rust implementation. In the latter, you're | most likely implying using mutable collections, fixed size | structs, primitive types, and a tighter memory allocation | surface. And not surprisingly, those are the same changes | you'd make to your Clojure code base to speed it up (most | likely). | burnthrow wrote: | > Clojure can only ever match Java in performance | | You're technically correct, but the typical Java program making | heavy use of threads has inefficiencies (and incorrectness) | that would be avoided with Clojure's higher level async APIs. | As it's easier to write idiomatic, performant C than the | "faster" ASM. | systems wrote: | i like how in the end the system was replaced by a database | solution | | every language need easy access to a query-ble database many | problems are a lot simple when solved declaratively as a query- | ble database | | the relational model, is functional, and is a very good solution | to a wide range of problems | | i think the Sqlite engine should be integrated in the standard | library of every language, and either use sql, or the language | can provide a native sql alternative in the original language | itself, or we can create a new standard language (because yes, | sql can be improved upon) | | I think Chris Date D language can be a place to start to | investigate SQL alternative , or as a language that can be more | easily emulated in other languages | secondcoming wrote: | 'queryable' is a word! | [deleted] | wwweston wrote: | What would you say the advantages of Date's D over SQL are? | systems wrote: | This is from memory, and i saw few examples but what i recall | the syntax was less dsl-ish SQL is a DSL, it is not a general | purpose language | | Chris Dates' D ( not to be confused with Walter Bright D) | looks more like a regular programing language | | And was functional | | So I think an language can add a library or an extension that | act like D, or simulate D | | Check this to get an idea | file:///C:/Users/ammselshishini/Downloads/Rel-and-Tutorial-D- | Quickstart.pdf | | Also, as I recall, the Notion of RelVars made it sound | functional | | Each Relational Operator, returned a RelVar So you were | passing RelVars between operators until you get the result | you want, which was a RelVar | | Anyway this is from memory, so I maybe be wrong on many | things .. but again from memory D or tutorial D sounded a lot | less DSL-ish and lot more functional than SQL , which was an | improvement in my mind | ithrow wrote: | The articles makes a good a case of how Rust can look as clean as | other high-level programming languages when writing high-level | business logic. | dgb23 wrote: | I agree that Rust has some really great concepts. The example | code makes heavy use of traits for example, which are very | ergonomic and provide a dynamic feel. | | The context is a rewrite AKA runtime optimization. So the | result is already understood. A great use-case for Rust is top- | down implementation. | | Also the code doesn't show any of the more painful cases. From | the article: | | > There are some inefficiencies visible here, and they're | probably the most important spots for performance improvements. | But they're still there as fixing them was too hard and/or | time-consuming for me. | | Resolving these "inefficiencies" is where Rust really shines. | Because it _can_ resolve them and does it internally consistent | on top of it. But at the same time, this is where you really | _slow down_ in development and need to think about the more | complex and intricate concepts such as lifetimes and borrow | semantics. ___________________________________________________________________ (page generated 2020-12-21 23:00 UTC)