Does clojure require nearly full test coverage? Or, is it okay to add tests for critical parts?
I will try minimal unit/property tests. If I need more, I will write more.
Game programming is one area where Rust is sometimes counterproductive, actually. https://deadmoney.gg/news/articles/migrating-away-from-rust I think doing that with any immutable discipline is going to be too hard sometimes.
I don't quite get zig, but I like it as a rust cross-compile linker.
https://actually.fyi/posts/zig-makes-rust-cross-compilation-just-work/
I think zig is a little better at unsafe low-level coding and not suited at all for high level coding
Some people prefer to pass an allocator around and complain that rust can implicitly panic on an OOM. And that is just not something I'm concerned about at the moment, but I guess zig is designed around no hidden allocations better.
I spend no time thinking about manual memory management.
Everything is on the stack unless it's wrapped by a reference type, usually for cocurrent shared state. Also use references in function args for big things, and try to avoid calling clone(). If you're fighting with the borrow checker maybe reevaluate if the design is wrong. That's pretty much it.
Strings, vecs and other days structures do their own heap stuff
If you're writing good clojure already, you likely can live without reference cycles and GC. Reference-counted smart pointers are sufficient.
I don't like Rust. I like clojure. This slack community is a wrong place to advertise Rust. Clojure programmers want simple languages which Rust is not. I read counter arguments like "garbage collectors just hide complexity under the rug". I prefer garbage collector to rust's compilation-time memory management which forces programmers to manage memory manually. If I didn't like garbage collector, I might as well write web services in Zig. People used to write web applications in C and C++. That's a step backward. Writing a web application in a low-level system programming langauge is a step backward. I don't want to deal with low-level concerns all the time. I don't need to aim for minimal memory consumption and maximum performance for a web application. That's not my use case. Even python is enough to serve my use case. There is a reason that people are tired of Rust evangelism. Rust evangelists push Rust where it doesn't belong, such as high-level web programming and scripting. Clojure programmers are here precisely because they don't want to deal with low-level concerns all the time. At least, clojure programmers don't push clojure where it doesn't belong. If I didn't use clojure, I would rather use python and javascript than rust. Rust would be my last choice for web development.
If I used Rust for web development, I will need to use async rust which forces me to learn rust memory management as well. Async rust is painful at this moment. I want garbage collector. I'm okay with world-stopping GC from time to time. Why would I want to torture myself with Rust for web development?
I ditched Haskell after finishing a 7k LoC haskell program because of its complex type system and cumbersome monad transformers. I don't want to go back to that level of complexity again unless I absolutely have to. I hate the complexity that comes with high levels of type safety. Garbage collector provides memory safety at the cost of stopping the world from time to time. My use case doesn't require ultra low latency. I don't want to pay for safety at the cost of simplicity. I would note that Rust doesn't prevent memory leaks, either.
I'm more trying to explain what it's actually like to use it than I am trying to persuade you. But I guess the dead horse is beaten enough.
I created a reddit thread, which is probably a better place to have this discussion: https://www.reddit.com/r/rust/comments/1o4fmlg/rust_is_a_lowlevel_systems_language_not/
I also prefer JVM's virtual thread to rust async. I don't like async which colors functions. Rust can't have virtual threads because it doesn't have a runtime.
Virtual threads are pretty cool!
Rust also doesn't enforce persistent data structures by default. Why would I want to ditch clojure for something that doesn't come with persistent data structures by default?
It sounds like a huge step backward.
I changed my mind on those when I did full-time work in ocaml. Stack-allocation is faster than what the JVM does, starting with the assumption that everything's a heap object but some can get optimized to stack. Most concrete data structures are small enough to fit in cache and if it's bigger than that, you probably want a database anyway. Persistent data structures are for medium-size-data, which is not really in the programs I write. You likely don't have very wide maps, do you? You have high-levels of nested trees with low cardinality, so you're not benefitting from the logN.
send the pitchforks
Persistent data structures are for peace of mind. I get weird bugs without persistent data structures even in small programs.
oh, I mean, immutable data is great! I just don't care for the HAMT tries
just make copies
And, you give up the performance advantages of Rust by making copies.
You are merely addicted to Rust. I'm not addicted to clojure or anything.
I was addicted to clojure
To me, clojure is just a boring language I have to get done with and move on from.
performance of copies is faster than you'd think if you aren't on a JVM
I think Rust can be fun, but I don't want fun. I want simplicity and utility. Rust is anything but simple.
It's a complex compiler, but much simpler at runtime, since there's no runtime
I think you should really go to Rust community if you want to talk about Rust that much.
I would consider Rust if it was a particularly good fit for my use case, but it's not. You are merely trying to use Rust everywhere because it's fun.
I think I'm just not making my point clear, but that's ok. ¯\(ツ)/¯
I'm just asking you to expand your view, for example there's some fun nuggets in that reddit thread: > (some guy's) opinion about garbage collection is and has always been that it's a bad resource management solution because it only cares about memory, which is almost always not even the most limited resource on the system, and the only thing that it really does is prevent memory leaks, which are not even undefined behavior, at the cost of not making object destruction and resource deallocation predictable. These facts often force working against the garbage collector itself by implementing strategies to guarantee that even if an object is referenced somewhere, its resources are predictably deallocated, so there's absolutely no benefit in garbage collection that is also not provided by reference counting with weak referencing.
I don't need to expand my view. I just want to write something in a sane manner and be done. The exploration phase was finished a long time ago.
Isn't Rust's whole deal that if you can statically prove that nothing else is mutating the same thing, then mutate away? Which means it would be pretty different in practice in that you'd still be writing mostly imperative code no?
You don't have to mutate things if you don't want to. No one is forcing you to use STM in clojure, either.
Sure, but I mean, the main ergonomics of the language would be imperative no?
Like mutable variables, for-loops, while-loops, if/else statements, mutable structs, mutable lists, maps, and so on? Or am I off base?
It depends on what you're doing, but I think the imperative parts are stricter than something like java, and it's just a different experience.
I had no problem with janet's garbage collector. It releases memory in a timely manner. I also never had problem with python's garbage collector.
ah, python is refcounted!
Well, I know it's not classic OOP like Java, and more uses Trait polymorphism and interfaces. But it seems the experience would be very different from a functional language like Clojure, as it would really just be imperative programming.
It's like an unholy marriage of ocaml and C++, I guess. Not quite as FP as haskell, the monads are not in your face at all.
FP doesn't require monad, and monad transformers suck. I prefer STM to monad state transformers.
More expressions than statements, eg break is an expression. So is if/else
let result = loop {
if b > 10 {
break b;
}
let c = a + b;
a = b;
b = c;
};valid
Ya, I guess I see it has let, match, ref, which maybe can make it feel a bit more expression-ish, but I also see statement if, loop, for, mut, return and so on.
lol, what would the result value be? The value of "c" ?
Oh, the value of b when it breaks out of the loop
Not gonna lie, that looks a bit gnarly 😛
it'll loop forever unless you hit break, so it works out!
What's fun about that one is I just guessed it might work and stumbled into it one day
Ya, but also nothing would stop you from say doing:
mut lol = 0
let result = loop {
if b > 10 {
break b;
}
let c = a + b;
mut = c;
a = b;
b = c;
};I want to be a businessman. You want to program for fun. We are doing two entirely different things. I already decided to use clojure, clojurescript, and python. There is no point in expanding my view, exploring things, or whatever. I want to focus on my business.
I'm a professional programmer, not a businessman, that's an interesting point, but I'm not sure clojure is the most pragmatic for founders unless you just know it the best already
I used to write clojure and haskell, and I got fed up with haskell. I already don't like Rust before even learning it because I got fed up with complexity.
Neither is rust, tbh. Probably typescript.
I didn't like Raku for its complex syntax. Rust's syntax doesn't look no less complex than Raku's. Rust looks more complex than Scala.
I think when people say Rust and Clojure share similarities, or are more similar than you think. It's because they share a bit of the same ideals. 1. Not into classical OOP 2. Thinks mutation is too hard to track in concurrent/parallel contexts 3. Likes macros But I feel they approach that from opposite sides.
never heard of raku 👀
Raku is perl's cousin. Perl was also known for complex syntax, but Raku's syntax is just more complex.
But Rust kind of misses on some of Clojure's ideal too: 1. Platforms are the future (hosted nature), access to tons of existing stuff 2. Lisp <-- probably the big one 😛 3. Functional But then Rust also has: 1. Zero-cost abstractions 2. As fast as C++ 3. Can go down to lowest-level
I precisely want to avoid low-level access which increase complexity. The combination of zero-cost abstractions, performance, and low-level access increases complexity.
The platform hosted-language thing is a false economy. Java can catch up, clojure struggles to have a killer app that needs to be written in clojure instead of java.
Clojure's killer app is web development. I use clojure where it shines.
Kotlin is a JVM language, but it has a standalone native compiler
Why did they bother?
They bothered because they are idiots with a lot of money.
Even if I had 10 billion dollars, I wouldn't do something like that probably.
If Rust is your choice, then stick to it, but don't talk me into using it. Use Rust for web development, or go fishing. I don't care either way.
Just pick one language, and work on it.
Java can't really "catch up". Unless it too became a functional lisp with a REPL 😛
The reason Kotlin did native I'd imagine is iOS 😛. Because iOS doesn't allow VMs like Python or Java on it.
For me, I'd need a Lisp with good live REPL workflow to consider an alternative. There is Carp, it advertises itself still as very experimental, so I haven't tried it yet. I feel like the "needs to be written in" idea does't really make sense to me. You can kind of write anything in anything really nowadays. The real constraints are more compatibility, like targeting the browser, targeting a game engine, a specific OS, platform, hardware, etc. Certain specific runtime characteristics it's possible some runtimes behave better, super fast startup, crazy small bundles, super low memory footprint, and what not. But in practice you can still make the same app and live with it. Which is why I optimize for my own joy when coding, the interactivity of the experience, the expressive power, the editing ergonomics of navigating and modifying the source code, etc.
Carp isn't good for me.
In that sense, if I couldn't choose Clojure because of a hostile to it work environment, it be more that maybe I'd be attracted to Rust over Java. In that sense I feel Rust compete more with Java than Clojure, Clojure is much more unique in look and feel.
You don't get to choose a language you like. The choices are forced upon you by the kind of things you write.
I just happen to like clojure and clojure happens to be a good fit for web development. I couldn't force clojure in system scripting because I had to write an entirely new clojure dialect for system scripting from scratch.
It's million times easier to change what you like than to force what you like to work for your use cases. I don't like Rust, and I don't have to force Rust to work for web development.
Well, that's the ideal. But I feel you can more often than you think pick the language you like for anything you want to write, if you're okay with the trade-offs or dealing with sometimes less than ideal setups. Like you can use Clojure for scripting, but you didn't want to let go the system package managers. Which I totally respect.
I would still force myself to like Rust if Rust is a good fit for my personal use case.
Oh for sure haha. But I sometimes go the extra mile to use what I like 😛. I wouldn't have attempted to bring in all of the Clojure deps into Linux package managers, so ya that I agree, too much work. But you know, dealing with Graal and native-image, dealing with ClojureScript pains, things like that though I endure 😛
I forced myself to like janet instead of shoving clojure into system scripting niche.
Babashka is a project scripting language, and it can produce uberscripts and uberjars which can be run by babashka. Thus, you can kind of get portable babashka programs because you can carry uberjars and uberscripts, but it is not a system application. It's a portable application. If you are okay with portable applications, you can use babashka.
Alright, time for bed for me. TTYL
I am writing rust on the only clojure team in a java-centric org, so I'm both trying to enjoy writing clojure again and trying to find more ways to use rust.
There is a kubernetes cluster that is so overprovisioned with JVMs that they think they can't afford to run our rust http sidecar, so instead I gave them a JNA FFI jar over the rust. It still amazes me it works at all, but why so many jvms?
My thing handles 10k req/s on 20 threads and uses 50 megabytes of memory total.
Can a java program run with less than 1GB these days?
I don't care about the perf. I care about reliability and not getting woken up at night.
Oh and they're stuck on java8 :P
We have a large clojure test suite that a/b tests this rust impl and the earlier clojure impl that preceded it (that happens to use different ffi to rust as well)
The punchline is I've found and fixed more inconsistencies in the clojure by bringing in that rust lib than the clojure tests have found bugs in the rust.
I can't speak for Haskell or OCAML, but for Java and Scala, we didn't have anymore tests on our Clojure code base than our Java or Scala code base. If you go and look at a lot of the open source Clojure libraries too, I feel there's not a whole lot of tests either.
To be clear, I'm not saying I have no tests. But a happy path test generally is good enough. And for certain things where there's very important nuanced edge cases test for those to be sure they're not accidentally regressed on.
Happy path tests are going to give you the "type regression" check you need.
What is happy path?
Happy path is like testing the typical use.
Say you have a test for +
Happy path test would be like
(= 10 (+ 1 2 3 4))
Where edge case test would be like
(= ? (+))
(= ? (+ 1))
(= ? (+ -1))
(= ? (+ nil))
(= ? (+ "1" 1))
To me, happy path is no test and no bug and no problem.
I think clojure offers a practical tradeoff between freedom and discipline and safety. It doesn't obsess with total safety as haskell and rust do. The attempt to achieve total safety increases complexity, reduces freedom of expression, and doesn't actually make code safe. Total safety is impossible. Rust is not safe. Haskell is not safe. Both languages have to provide escape hatches that people can abuse anytime. Transient data structures, persistent data structures, GC, and STM provide discipline and enough safety without sacrificing freedom of expressing actual logic. A complex type system and memory safety without GC restrict freedom or make coding cumbersome. I don't need to ditch garbage collector because I'm not writing a real-time game engine that can't have world-stopping GC. There is no substitute for developer discipline. If you don't hire a disciplined developer, you will get shitty code in any language. A developers doesn't have to be the best, but has to be disciplined. Don't chase total safety. Don't chase safety at the expense of freedom. Even in real world, the pursuit of total safety doesn't make people actually safe and only erodes freedom. There is no substitute for discipline and freedom. If you chase safety at the expense of freedom, you lose both.
Haha, well I mean specifically "happy path testing". But yes, the happiest path is it all just works 😁 I agree about the sweet spot trade-off.
That said, for low-level system programming and game engine, I don't mind rust or zig. Zig is much simpler than rust while providing some memory safety without GC. For high-level system programming, jank and basilisp can be used. Babashka can't be used as a system programming language. It is a project programming language or a scripting language. I don't see the advantage of web development in Rust. We already have clojure and clojurescript. In the future, we might even have basilisp for web backend programming.
One thing worth noting about Clojure is that it's strong, dynamic, and idiomatically functional + immutable, a relatively rare combination (only Erlang and Elixir immediately come to mind). That specific combination excludes a whole variety of bugs, and particularly those related to state.
In terms of tests, my default recommendation is to invest time in property-based testing if you care about correctness. Unit tests will catch trivial bugs and errors, but their ability to catch subtle cases is about 10x-100x less effective than property-based testing (anecdotal estimate). Of course, unit tests are also much more trivial to write. Nowadays, any decent LLM can write you a working unit test almost always on the first try.
I still don't care. You are in a company that can afford to hire multiple developers. Each developer can specialize in a different language that's good for different things. You want me to write Rust? Pay me a lot.
As I wrote above, Rust can be used for squeezing performance in a company that's in high demand. I can always hire a Rust/C/C++/whatever developer later. Safety and reliability come with their own cost. You are only looking at benefits and ignoring the costs. I can even justify haskell if I was only considering benefits and ignoring the costs. I can justify haskell all day if I really wanted. I will still consider Haskell in the right contexts, but I will not usually even think about Haskell. I can give you endless reasons that you should use haskell instead of rust, but I won't.
Please stop pushing Rust so hard in clojurian slack. It's obvious that you are pushing Rust hard.
Clojure and java are good at certain things and bad at other niches. I'm aware of that. Haskell may be suitable in some contexts, but I don't like Haskell and Rust personally. If Rust is actually a good fit and not using Rust is the the difference that kills my business, I will gladly force myself to learn and like Rust. But, until that happens, I won't consider it.
To me, clojure is just a stepping stone and just a tool. I like it, but I am not going to push it where it doesn't belong.
Don't push specific things where they don't make sense right now. Right now, Rust doesn't make sense to me. I don't know when it will make sense. Don't give me scenarios that I won't find myself in anytime soon.
Python, Raku, Haskell, Rust, etc,.. I don't care. Just get the job done quickly. I will do whatever makes sense "right now". Rust isn't a particularly good fit for web development. It can be used for web development just as C, C++, and Zig can be. But, that doesn't mean these languages are my default choices for web development from the beginning. I wouldn't choose C, C++, Zig, Nim, Rust, Racket, guile, etc, ... as default choices for web development.
You are just making artificial reasons to use Rust. I want you to go to Rust community if you want to continue preaching Rust. I don't want to hear more of Rust sermon.
If you are frustrated by the fact that you aren't allowed to write Rust as much as you want, then find a new job, or create a Rust business, or write Rust as a hobbyist. Don't come here to tell others to use Rust. What do you think Rust is? Is it the thing that's going to save the world from destruction? Get a new job, or create your own business, man.
How annoying would it be if a clojure developer is nagging other people to use clojure in a ruby shop that writes ruby on rails? Everything is already written in Ruby, and you want us to adopt clojure? I wouldn't suggest that. Get a new job.
I hate Rust evangelism culture. I didn't care much about Rust. Now, I actively want to avoid Rust because of the community. Don't bother other people.
If Rust was so great, then we would be the ones who are banging on your door for advice?
ZGC on Java 25 is an effectively pauseless GC (it comes with the JVM, but it's still not the default; use a flag to enable it). In my tests, it's pretty impressive so far. No hard stop-the-world pauses, just short slowdowns while the GC is running, even when calling (System/gc).
One more reason to prefer GC to Rust borrow checker.
From my brief research in Rust, to implement persistent data structures (similar to Clojure), one has to use Rust's Arc (and not their default borrow checker). Arc runs at runtime (!), unlike the borrow checker, which is "cost-free" (in terms of performance). But I far from a Rust expert, so would love to be corrected if necessary. https://github.com/orium/rpds
And when we start talking about Arc, some anecdotal reports say it's actually worse than GC in many cases, especially once memory gets fragmented, etc, etc. It's uncanny what the JVM can do because it has total control over the location of objects: it can shift them to new memory addresses as it sees fit, compacting memory to reduce fragmentation. In Rust/C/etc that's "your problem" (again, that's my outsider's understanding).
Hmm, the reason to use it was not arbitrary. People before me decided to build around Cedar https://docs.cedarpolicy.com/policies/syntax-operators.html I expect as time goes on, there's going to be more key functionality in different domains where the rust impl wins.
Here's the persistent data structures I've seen but have no need to use https://docs.rs/im/latest/im/ , and i felt the same way in ocaml
Arc is a reference-counted smart pointer. It's ok to build around. It's not zero cost, but it's fine.
You can replace glibc allocator with a different one if you need to, but in 8k lines i have like 10 Arcs.
You would be surprised how much you can get done with predominantly stack allocation
You can run JVM apps with under 50mb of memory. Bigger apps might need to be under 250mb and so on. If you native compile them they run with even less. Of course you incur more garbage collection passes. That's the trade off with explicit memory management or reference counting. But you get to just never have to worry about it, no wondering if you have cycles, no thinking if you have a graph and need Rc, and so on.
No. No....
Stop talking about Rust here. I am notified of new comments on this thread.
Go somewhere else.
Just admit that the general perception of mutability and the value of GC clojurists tend to take is based on how it goes wrong in java
The majority of people I meet who aren't on the JVM actively avoid JVMs
I don't use clojure because of JVM. I couldn't care less about JVM although virtual threads are cool.
If clojure wasn't hosted on JVM, I wouldn't touch JVM.
Yeah, I'm curious to see how jank-lang does
I like repls and s-expressions. ¯\(ツ)/¯
typed racket might not be so bad, except no one uses it
Typed racket is not worth thinking about.
@gtrak I urge you to stop trying to expand your mind with new programming languages. Typed racket is just another distraction. Just pick one language and one niche, and stick with it. You don't live 10,000 years and get to live the first 1,000 years freely sponsored by someone else. If you screw up the next few years, your life will be ruined. You live on human timescale. You probably only have 10 years to shit your shit together. Focus on being useful to other people. Typed racket and other programming languages won't help you become useful to other people.
clojure had to justify its existence not too far back, and I would prefer if it got better. What if everyone thought like that and stuck to java?
I don't care. It doesn't matter in the grand scheme of things. If there was only java, I would still force myself to like java.
Clojure just happens to be a good boring language for web dev.
Linus torvalds picked C language and linux kernel as his niche. Rich hickey picked clojure and never looked back.
Pick one. Only add more after you stabilize your niche for years.
A lot of people did the same and they didn't become Linus or Rich for it
But, they are still far ahead of people like you who dabble with everything and don't specialize in nothing.
bold statement, lol
Dabblers aren't good at any one thing. You are only paid as much as your best specialty. If you learned how to build a house with your hands and how to build a computer and how to build web app, you won't be good at any one of them.
Without divions of labor and specialization, you become poor.
I was a tech lead for a ruby team before this, and dabbling was enough
I've worked effectively in lots of languages, even the ones I don't like
I don't expect everyone else to do that, but it could work, right?
Then, your specialty probably isn't a specific language. It's probably CRUD web dev? I still would avoid learning a new language unless I have to.
You could be so much more if you focused on one niche.
The domain was new to me, but it's just systems at the end of the day. DBs, message queues, observability. It didn't matter that it was ruby, except that ruby made it horribly slow.
There aren't a lot of generalist jobs out there, but I always find one.
Because most programming jobs don't require high expertise. But, that's not going to suffice for earning a lot of money or achieving greatness.
Ah, yeah, I don't believe in making high-risk big swings kind of bets with my time.
It's not high risk.
That's why I'm not a founder.
Being an expert C++ game engine developer isn't exactly a high risk endeavour.
In any case, learning a new programming language just for fun isn't going to help. It will just waste more of your time.
gamedev is maybe not the best example https://cjc.utppublishing.com/doi/10.22230/cjc.2006v31n3a1771
Earning a lot of money isn't about working 4 hours a day.... If you just want to be happy, you can be a starbucks employee. No stress, but little pay.
Life is too short to waste time on learning programming languages just because you might think they maybe help in the future. If you are going to learn something, you better need it now.
I thought like you for about 10 years, but it was refreshing to enjoy it again.
I'm not passionate about programming. I'm only passionate about wealth for now. Clojure is just a stepping stone toward wealth for me. If my business grows, I won't code outside my tiny system scripts.
I used to be passioante about programming, but I discovered I was rotting in poverty because I was only thinking about comfort and fun. I don't want to spoil myself with comfort and fun anymore. If java is going to pay me a billion dollars, I will write it instead of clojure. If diggin the ground pays me billions of dollars, I will dig the ground. Concrete business moguls don't make love with concrete dolls. I don't make love with clojure dolls. I just want to have some wealth...
A new programming language won't help you deliver business value. Learning typed racket definitely won't. Learning guile scheme and guix system won't help you deliver business value if you can spend your time on just delivering business value instead of learning these things. I would just use a good simple programming language to deliver the value. Time to market is important. With speed, you can fix backend problems later.
agreed, that's why I never learned haskell. I learned ocaml for one job b/c it was impossible to hire ocaml devs, so they were willing to wait for people to learn it during onboarding. It took a couple of weeks, same with rust.
Clojure broke my brain and made learning new languages easier, I think
I definitely regret learning haskell... I already know how to write clojure. I'm sure Rust will be just as cumbersome as haskell.......
I'm not saying there's no cost, just that you might be overstating the cost. You like clojure and don't want to consider other languages, I have no problem with that. I just want those things to be represented accurately.
Not every clojure dev we hired to do ocaml had an easy time onboarding, and they were let go.
I also regret learning Raku because of its complexity in comparison to relative lack of expressive power.
Rust isn't going to be any less complex than Raku. So, I stay away from complex languages.
I'll move the conversation to off-topic: https://clojurians.slack.com/archives/C03RZGPG3/p1760464964839769
The most complex part of clojure is transducers, so clojure isn't nearly as complex as other big languages.
🙊
There's nothing special about Clojure in this regard, so it by itself does not require anything. You might be alluding to the fact that it's dynamically typed, to that I can only add my own observations that the vast majority of bugs are not type-related.
I wasn't precise with my language. Is it sufficient to add tests for critical parts in production? Or, should I aim for nearly full test coverage in production?
I guess static type checking reduces the need for a lot of tests.
Unit tests with specifications and generative test data is very common in Clojure. I suggest that is critical for the public interface/api of the clojure system (incoming and outgoing). The amount of unit test code coverage is an engineering decision for the team and organisation, depending on many factors. REPL driven development can prevent errors creeping in when done well, although often use the repl experiments to help design valuable unit tests too I have found generative testing with clojure.spec or malli very effective in both commercial and personal projects. Using test selectors during development can be very effective at minimising the feedback time from testing
> I guess static type checking reduces the need for a lot of tests. That's what I tried addressing with my second statement above - I don't find it to be the case. With everything else being equal, including the amount and the scope of tests, a project being written in a statically typed language does not mean that that project is more reliable than the version written in a dynamically typed language. There are factors beyond just the "compiler checks that X can be done" one, and they aren't amenable to even a hypothetical perfect and practical type system that doesn't exist and probably won't exist for decades to come. But it's my own anecdata and a somewhat philosophical preaching. All the studies in the relevant domain have always been indicative at best, and their indications are stochastic as well.
IMHO, testing should always be prioritized by risk. Test the high risk areas first and the lower risk areas after that. It's your decision when to stop, because of diminishing returns. In an ideal world, everything would be tested. But if you don't live in an ideal world, prioritize the testing according to risk and consequences. The higher the coverage, the better, of course.
I can only speak of one-person projects so far when it comes to Clojure, and for those I don't find I need more unit tests compared to popular statically typed languages like C#, Java. Compared to something like Elm and Ocaml, however, I do feel a higher need to test the nil case explicitly in unit tests (and depending on how complex/nested the data structure is, that can be quite a few tests). I know Clojure and other lisps aren't the worst when it comes to their approach to nulls, but I have had quite a few cases where I was caught by surprise in practice. So now I end up being possibly too defensive on that front.
> Is it sufficient to add tests for critical parts in production? Or, should I aim for nearly full test coverage in production? We structure our app so that we can write mostly functions that take data, return data, and doesn't do anything else. Those functions are easy to test (example input, example output or example input with assertions on output). Those minimal "machinery" parts (like http server stuff) are things we don't spend too much effort writing tests for. It's hard to test, and it's often stuff you notice immediately if it's wrong. If the http server is called with invalid arguments, you won't see anything when running locally. So trying to test everything is probably be more painful than beneficial. To answer "shold I test X?", you'll have to feel your way forwards. If you spend all your time writing tests, write fewer tests. If you spend all your time debugging errors that could have been caught by better tests, write more tests.
If you have a functional core with a stateful shell, so you push all state handling to the boundaries, most of the application can be tested by unit tests without the need for mocking or complicated state preparation. So testing a functional core is easier than testing a default OO application.
In my opinion static typing prevents you only from the most stupid bugs. Often a single test case provides the same confidence as all the typing ceremony. Don’t understand me wrong. I like static typing, and it has some value and it is a good exercise and provides more reasonability while programming. But most code is so simple, that the effort does not pay off. So I recommend to write tests until you feel you have enough. I can tell you at least, that for me 100% systematic test coverage prevented me a few times from bugs in Clojure. But the effort was high. So I don’t know if it was finally worth the effort. And I also think that tests have a downside. If you change the code, you have to change tons of tests. All the time you spend for testing, you don’t have for programming and for systematic thinking. I think nobody can give you a finite answer, what is good for you.
I will try writing tests for critical pure functions. I prefer minimalist approach. That way, I can know how much test I actually need.
You need to test your code, but generally that's done at the REPL. After that, the amount of regression test you want to have depends. I don't feel you need that many in reality. They exist so that you don't accidentally regress on some behavior or property. I'd say that's most valuable when testing business logic and the more end to end features.
for me, tests are something I can do before asking someone else to look at my code. I don't want to waste a collaborator's time by making them fix my mistakes before they can get other work done. REPL is excellent for drafting out my own internals, but it isn't how I would ensure I am not breaking the rest of an application. it's very easy to have "don't push code that won't pass the type checker" and/or "don't push code that doesn't pass unit tests" as rules.
also, especially when working remotely, it's easy in functional style code (in any language) to have things that are so abstract that you can't really tell what they are for without unit tests.
The codebase I work in with experienced clojure devs has more lines of test code than impl, but a large portion of that is test data. I've done a lot of ocaml in the past and some rust these days, and my feeling is you need a large test suite in clojure for any nontrivial codebase, and not so much in static-typed HM-inference languages. Eg, test the happy path and rely on exhaustiveness checking for the other cases. You don't usually need to write tests for anything the compiler does for you, so you spend a little more time encoding your business logic into type invariants instead of writing tests for those properties. I've done it both ways, I prefer static types to be honest, but others will disagree with me here.
Another way to look at it is in clojure you may have to write more tests than some other language choices, but they're easier to write. You may have to apply more judgment to prioritize what to test, and there's some common gotchas and the usual dynamic-typed problems, but repls and homoiconicity make it less bad.
also data-orientation helps vs something like ruby
I was burned by haskell monad transformers and its complex type system that hinders type inference.
I'm not sure all that haskell stuff is necessary, but I haven't tried to use it.
I wouldn't use rust for web programming because it is large and complex? Why did you need a lot of tests for your clojure application? Just a lot of test data? Do statically typed programming languages spare you from a lot of test data?
I'm a minimalist, so I try to write minimal tests.
By the way, I wrote a 7k LoC haskell application. I hated the experience. It's cumbersome.
No, I think static types spare you from some paranoia of possibly missing an important case. Instead, you just feel friction when modeling things wrong and it might force some different choices upfront. I'm not sure there's any relation to our application domain and the testing, it's just what the developers ended up doing over time. I've worked on a 40k-line ocaml codebase (not sure how much of it I wrote), and wrote 7k lines of rust recently for this thing, and I didn't hate it generally. There were specific tooling problems that improved over time in ocaml, not rust.
It seems weird to choose a language based on how many tests you think it'll make you write?
No.
I already chose clojure and clojurescript as the languages for my new web service.
Technically, I can use haskell, rust, and ocaml for web programming, but I prefer clojure for web programming.
Yeah, I don't know what's clojure-specific about testing strategy, except you tend to model everything as pure interpreters over tiny data-dsls.
Haskell is way too complex with higher kind types, monad transformers, etc, ... OCaml may be good, but I haven't learned it. Rust is big and complex. There isn't a single statically-typed language I like, yet.
The rust complexity doesn't bother me, I don't have to know all of it to be effective. Borrows/lifetimes are a key thing, but they're worth it:https://cliffle.com/blog/rust-typestate/#a-simple-example-the-living-and-the-dead Eg, if you close a file you can't compile a program that tries to do something with it after
I have a bad habit of chasing fun instead of utility. Clojure and clojurescript are more than fine for web programming, so I use them. I'm sure Rust can be useful in the right contexts.
https://www.roc-lang.org/ seems to involve minimal static type burden, so it can be useful in the future. Just not now.
Ocaml has really nice inference if you just care about not having to explicitly write the types out, but I think I felt more 'burden' around them at the start: https://ocaml.github.io/merlin/editor/emacs/ Rust and typescript need to you write them out at function boundaries, but ocaml doesn't
For rust, VSCode/rust-analyzer can infer those anyway, so it's a couple of clicks to have it write them for you
I don't see the appeal of typescript. Static types on javascript is like lipstick on a pig.
Yeah, that complex type system is what you get when you try to accommodate all the dynamic code that's already been written instead of a designing a language without baggage.
It's as good as anything for web programming, I guess?
It depends on your requirements. The rust I'm working on can handle 10k rps for authz and JWT validation on 20 threads and uses like 50MB of memory max to do it. Nearly everything is stack-allocated. If you just care about code expressivity, you might pick something else, but I like working in it.
Rust is a low-level system programming language that's designed to be efficient, but it leaves a lot to be desired.
For my web programming, Rust isn't the first choice.
https://medium.com/tekyz-blog/discipline-the-backbone-of-an-exceptional-software-development-team-d2669d807893 In my next project, I'm going to test out freedom and discipline. There is no substitute for discipline. Rust ruined developer experience with memory safety without garbage collector. This time, I opt for freedom and discipline instead of total safety.
I may be a bit cynical but is the argument that dynamic languages don't give you a false illusion of safety? 😄
plenty of success stories out there https://www.allthingsdistributed.com/2025/05/just-make-it-scale-an-aurora-dsql-story.html
Rust is a good choice if you want to squeeze out performance without worrying about memory leak, but I don't need to do that in my web service project.
If I ever need to squeeze out performance, then I already don't need to write any code. I can hire rust developers later if I choose rust, and I don't have to write rust.
Hmm, let me put out there that I think the people that complain about the borrow-checker are usually OO or C background folks, not FP folks.
Maybe there's more of them to begin with, but I think clojure's immutable habits get you 80% of the way to doing that right.
I"m not against rust for all cases, but I don't want to be the one who writes it. I'd rather hire rust developers.
If you know you have a lot of self-referential data structures, then you might actually have a hard time with it, but most people don't really need that.
Eg EDN doesn't have cycles unless you put in atoms or something
I don't care if you write rust, I just think that it's a common misperception that the better perf comes at the cost of usability, which doesn't match my experience.
Rust isn't usable for me... I don't want to deal with rust memory safety in my web service. I'd rather pay someone to squeeze out performance if my web service is so much in demand that I need to hire people anyway.
Rust has haskell vibe, and I didn't like writing haskell... which forced me to write monad transformers and lenses.
I just want to focus on web service business logic. Clojure is good for that.
Clojure was designed for line-of-business(in situ) applications. It's suitable for people who want to focus on business logic.
Has rich hickey ever written a web service?
Yes? A lot?
He was a C++ guy
He also wrote common lisp and other languages.
And, it doesn't matter at this point. I already know how to write clojure and clojurescript. I like these languages. I don't want to spend the next few months on learning Rust now. Rust isn't an advantage over clojure for my use case.
Rust is very much like C++. I wouldn't write C++ personally, either. But, I will hire someone who writes C++ or Rust.
I suggest to try new things, but I also hope this works out for you!
I don't have time to learn everything that comes my way. If I learned everything that comes my way, I would grow old and die before I launch any business. I don't live 10,000 years. That's why we have division of labor. I run my business, and you write Rust. I hire UI programmers because I can't replace them with my own time.