4 ms·
Clojure is a lot of fun to tinker with, but man… I love my static types. I think I’d hate to work on a large codebase in Clojure and constantly be wondering wha
by christophilus 2y ago
Clojure is a lot of fun to tinker with, but man… I love my static types. I think I’d hate to work on a large codebase in Clojure and constantly be wondering what exactly “m” is.
- ilrwbwrkhv 2y agoYou wouldn't because of the repl. You would jack in and no exactly what m is.
- satvikpendem 2y agoWith static types, I don't have the use the repl at all, I can simply hover over it in my editor.
- Zak 2y agoWith a REPL-connected editor (and most have a way to do this), you can simply hover over it in your editor as well. Even though most languages can have a REPL today, few integrate it in the development experience the way lisps do.
- iLemming 2y agoCorrection: It's not that you "don't have to use the REPL"; you simply cannot even have it in that case. REPL-driven development is quite a powerful technique, and no, "many other languages too" don't have it. For it to be "a true REPL," it must be in the context of a homoiconic language, which Clojure is. Sure, static typing is great, but perhaps you have no idea what it actually feels like - spinning up a Clojurescript REPL and being able to interactively "click" and "browse" through the web app programmatically, controlling its entire lifecycle directly from your editor. Similarly, you can do the same thing with remote service running in a kubernetes pod. It's literally like playing a video game while coding. It's immensely fun and unbelievably productive.
- hombre_fatal 2y agoAs nice as nrepl/cider are, doing what amounts to setting a breakpoint in the middle of a function to see what `m` looks like isn't a replacement for knowing the type without executing code. It's just something we put up with.
- iLemming 2y agoI've been using Clojure for a while and I rarely ever wonder "what 'm' is" - that almost never happens, despite the language being dynamically typed. Data shapes in Clojure typically explicit and consistent. The context usually makes things quite obvious. Data is self-describing - you can just look at a map and immediately see its structure and contents - the keywords serve as explicit labels and the data, well... is just fucking data. That kind of "data transparency" makes Clojure code easier to reason about. In contrast, in many other PLs, you often need to know the class definition (or some other obscured shit) to understand what properties exist or are accessible. The object's internal state may be encapsulated/hidden, and its representation might be spread across a class hierarchy. You often can't even just print it to see what's inside it in a meaningful way. And of course, it makes nearly impossible to navigate such codebases without static types. And of course the REPL - it simply feels extremely liberating, being able to connect to some remote service, running in a container or k8s pod and directly manipulate it. It feels like walking through walls while building a map in a video game. Almost like some magic that allows you to inspect, debug, and modify production systems in real-time, safely and interactively, without stopping or redeploying them. Not to mention that Clojure does have very powerful type systems, although of course, skeptics would argue that Malli and Spec are not "true" types and they'd be missing the point - they are intentionally different tools solving real problems pragmatically. They can be used for runtime validation when and where you need it. They can be easily manipulated as data. They have dynamic validation mechanisms that static types just can't easily express. One thing I learned after using dozens of different programming languages - you can't just simply pick one feature or aspect in any of them and say: "it's great or horrible because of one specific thing", because programming languages are complex ecosystems where features interact and complement each other in subtle ways. A language's true value emerges from how all its parts work together, e.g., - Clojure's dynamic nature + REPL + data orientation - Haskell's type system + purity + lazy evaluation - Erlang's processes + supervision + fault tolerance What might seem like a weakness in isolation often enables strengths in combination with other features. The language's philosophy, tooling, and community also play crucial roles in the overall development experience. If one says: "I can't use Clojure because it doesn't have static types", they probably have learned little about the trade they chose to pursue.
- kamma4434 2y agoThe compiler should know it for you, so you cannot get it wrong no matter what. The REPL here is a band-aid not a solution. I mean, I love Clojure, and used it for personal and work projects for 10+ years, some of which have hundreds of stars on github. But I cannot count the time wasted to spot issues where a map was actually a list of maps. Here Elixir is doing the right thing - adding gradual typing.
- iLemming 2y ago> But I cannot count the time wasted to spot issues where a map was actually a list of maps. Sorry, I'm having hard time believing that. I don't know when was the last time you've used the language, but today there are so many different ways to easily see and analyze the data you're dealing with in Clojure - there are tons of ways in CIDER, if you don't use Emacs - there are numerous ways of doing it in Calva (VSCode) and Cursive (IntelliJ), even Sublime. There are tools Like Portal, immensely capable debuggers like Flowstorm, etc. You can visualize the data, slice it, dice it, group it and sort it - all interactively, with extreme ease. I'm glad you've found great fondness for Elixir, it is, indeed a great language - hugely inspired by Clojure btw. You still don't need to bash other tools for no good reason. It really does sound fake - not a single Clojure developer, after using it for more than a decade, would call a Lisp REPL "a band-aid and not a solution". It smells more like someone with no idea of how the tool actually works.
- kimi 2y agoMaybe it's so. Or maybe you run my code in your deps. As you can see, there is at least one Clojure dev who thinks so. I found spec very useful and damn expressive (and I miss it in other languages), but again that's runtime. I know Rich says such errors are "trivial", but they waste your time (at least mine).
- iLemming 2y agoTo each their own. Some people (not me) say that Rust's pedantic compiler feels like bureaucratized waste of time akin passing through medieval Turkish customs. For me personally, working with Clojure dialects feels extremely productive. Even writing in Fennel, which is not Clojure, but syntactically somewhat similar is much faster for me than dealing with Lua. Even when I have to write stuff in other PLs, I sometimes first build a prototype in Clojure and then rewrite it. Although it sounds like spending twice the effort, it really helps me not to waste time.
- brabel 2y agoFor a single run of your code. That's absolutely no guarantee that m will always have the type, no matter how you got to the call.
- iLemming 2y agoWhat do you mean? Clojure is strongly typed language - every value always has a definite type. It's not like Javascript. Types in Clojure are fixed and consistent during runtime, they just aren't declared in advance.
- brabel 2y agoDo you think there's only one path to your function? There could be thousands in a big system. The type of the value you'll get will depend on the path you call it from. Even if it's only one path, you could easily have code doing stuff like this: if x > 10: call_my_function 10 else: call_my_function "foo" Can't you see that unless you test every path, you won't know what type you will receive??
- iLemming 2y agoYour contrived example is a bad smell in ANY language. No sensible coder ever writes a function that accepts both numbers and strings - handling multiple types should be done through proper polymorphic constructs, not arbitrary conditional branches. There's a wide spectrum of correctness guaranties in programming - dynamic weak, dynamic strong, static, dependent, runtime validation & generative testing, refinement types, formal verification, etc. Sure, if your domain needs extreme level of correctness (like in aerospace or medical devices) you do need formal methods and static typing just isn't enough. Clojure is very fine, and maybe even more than just fine for certain domains - pragmatically it's been proven to be excellent e.g., in fintech and data analysis. > Can't you see that unless you test every path ... Sure, thinking in types is crucial, no matter what PL you use. And, technically speaking, yes, I agree, you do need to know all paths to be 100% certain about types. But that is true even with static typing - you still need to test logical correctness of all paths. Static typing isn't some panacea - magical cure for buggy software. There's not a single paradigm, technique, design pattern, or set of ideas that guarantee excellent results. Looking at any language from a single angle of where it stands in that spectrum of correctness guaranties is simple naivety. Clojure BY DESIGN is dynamically typed, in return it gives you several other tools to help writing software. There's an entire class of applications that requires significantly more effort and mental overhead to build using other languages. Just watch some Hyperfiddle/Electric demos and feel free to contemplate what would it take to build similar things in some other PL, statically typed or whatnot. https://www.youtube.com/watch?v=nEt06LLQaBY https://www.youtube.com/watch?v=nEt06LLQaBY
- askonomm 2y agoYes, you would know what it is at that moment. You would not however know if what it is at that moment in time is actually correct, or what the expected shape is, without deconstructing the entire function that is the receiver of the data. That's where static types are useful - I can just hover my mouse over a function and it will show me what is the expected input, and expected output, and I do not need to read and understand the contents of the function to know if the data is correct, because it would throw an exception if it is not, like if a string suddenly becomes a number or is missing a piece of information, etc.
- ilrwbwrkhv 2y agoTheoretically, yes. And trust me, I loved static types. I use Rust for almost everything. However, the programming loop or the iteration loop that you get into with Lisp, especially with things like Common Lisp, it's not that much of a concern. But I agree with any other language which is not a Lisp, a static type system is far superior.
- tmtvl 2y agoOne of the myriad reasons why Common Lisp is far superior to Clojure is the ability to (declare (type Integer m))
- Zak 2y agoTyped Clojure is available as a library.
- joshlemer 2y agoBut just about nobody uses it right? Probably a reason for that.
- knubie 2y agoI think the consensus is that it is not really mature enough for general adoption. Also, most people prefer to use one of the specification libraries that are available (spec, schema, malli). These allow you to do a sort of design-by-contract style of programming.
- koito17 2y agoUntil recently (2023), the type inference was very weak and did not work with higher-order functions (map, filter, reduce, etc.). As a result, Typed Clojure was practically unusable for most applications. That has changed as of last year. For instance, the type checker can now handle the following kinds of expressions. (let [f (comp (fn [y] y) (fn [x] x))] (f 1)) This expression was a type error before early 2023, but now it is inferred as a value of type (Val 1). Unfortunately, many Clojure users think types are somehow a bad thing and will usually repeat something from Rich Hickey's "Maybe Not" talk. I've worked with Clojure professionally. The codebases I've seen work around dynamic types by aggressively spec'ing functions and enabling spec instrumentation in development builds. Of course, this instrumentation had to be disabled in production because spec validation has measurable overhead. Although Typed Clojure has made remarkable progress, the most editor tooling I recall for Typed Clojure is an extension to CIDER that hasn't been maintained for several years. (The common excuse given in the Clojure community is that some software is "complete" thus doesn't need updates, but I have regularly found bugs in "complete" Clojure libraries, so I don't have much confidence here). Overall, if one wants static typing, then Clojure will disappoint. I still use Clojure for small, personal-use tools. Having maintained large Clojure codebases, however, I no longer think the DX (and fearless refactoring) in languages like Rust and TypeScript is worth trading off.
- medo-bear 2y ago[dead]
- f1shy 2y agoI seem to be the only one in HN and maybe the world, who worked with decent enough codebases where dynamic typing was no problem at all. Not that I “feel” it was no problem, but there were no bugs found that could be traces down to that. It was not a small codebase.
- iLemming 2y agoIt's not just that. Static types do help, yet dismissing an entire language because of a single aspect of it is extremely short-sighted. It's like rejecting Russian or Turkish, only because they have no concept of definite or indefinite articles. Sure, Clojure is dynamically typed, but it is also strongly typed. That in practice means that for example Clojurescript when compiling to Javascript enforces those type guarantees, sometimes emitting safer code than even statically typed Typescript cannot.
- f1shy 2y agoThat is exactly my feeling, like lately everything must be “safe” and statically typed. While I do see some (big, sure) pros I see also some cons that I feel are systematically ignored or neglected. For me it seems to be a kind of fade/hype… but maybe I’m just connected to the wrong news feeds.
- iLemming 2y ago> maybe I’m just connected to the wrong news feeds I don't think so. It's just like said in another comment: For everything, there's a trade-off. Some just accept those trade-offs, build their vision, and launch it into the world; Some waste time, lamenting that reality doesn't align with their ideals. Static typing works - just like formal methods, just like dependent types, just like unit testing, just like generative testing, just like many other different ideas and techniques. They each have their own place and use cases, strengths and weaknesses, pros and cons. Picking one single paradigm, technique, design pattern, or methodology - no matter how amazingly powerful they are - and just dogmatizing and fetishizing it is simply immature. Reaching the point where you clearly understand that there are truly no silver bullets and everything is about making compromises is a sign of professional growth and true, genuine experience.
- troad 2y agoAs a programmer with almost exclusively statically-typed compiled language experience, I used to strongly believe this too. In the last year, though, I've seriously toyed around with a number of dynamic languages, really trying to grok the dynamic mindset, and it's been very eye-opening. I never expected quite the degree of productivity boost that I have felt in these languages, and I must admit that I've also found many of them quite joyful to work with. Dynamic languages are profoundly creative, in all the senses of creative. I've found myself thinking about my programs in a totally different way, which has been really lovely (and honestly a timely reminder of what I loved about programming in the first place). To be fair, I will readily say that the lack of static analysis really does bite when refactoring, though I think that good design principles and the overall productivity multiplier may offset that cost (also unique, descriptive, grep-able names!). I guess I've also seen enough C++ template spaghetti to know that static typing is no panacea either. I don't know to what extent I'll use dynamic languages going forward, though for now I'm kind of in love with opening up a window into the computer and building up my digital sandcastles. Many of these languages also have a great FFI story, which is making me dream up cool bilingual projects that play on the strengths of both approaches. All in all, no regrets about my adventures in dynamic-land.
- rhubarbtree 2y agoEnlightenment. I trod the same path. The solution to refactoring is tests, but having simpler code greatly improves this anyway.
- pntripathi9417 2y agoFor us the combination of malli and clj-kondo worked really well. Also we haven't faced that problem yet, as the codebase is fairly small. But I can totally see when types become quite useful when navigating large codebases.
- askonomm 2y agoHaving worked with large Clojure codebases I found the Malli/Spec duct-taping of types to be a poor man's statically typed language, especially with the developer experience being quite poor. While it will runtime validate, I still have no idea what shape anything is by just hovering it - having to constantly navigate between the definitions file, and they are also more cumbersome to use and maintain. I've come to the conclusion that it is just a better experience using a language that already has static types for large projects, than trying to make a dynamic language have similar things. Having to wrap every function in a error boundary to get somewhat of a meaningful debug experience is just .. awful.
- stefcoetzee 2y agoSome contributions to this discussion here: https://youtu.be/vUe3slLHk20?si=57O9CdyPakxrD4Rx&t=1469 https://youtu.be/vUe3slLHk20?si=57O9CdyPakxrD4Rx&t=1469
- iLemming 2y ago> duct-taping of types to be a poor man's statically typed language On the other hand, they allow you to do some very interesting things like using specs for complex validation. Once written specs can be then used for generating data for both - UI testing and property-based unit-tests. We once have build set of specs to validate an entire ledger - imagine having to be able to generate a bunch of transactions where numbers intelligently build based on previous entries. Other languages even though have similar capabilities, like type providers in F#/OCaml, zod in Typescript, quckcheck/scalacheck in Haskell & Scala - Clojure is quite unique here in combining runtime validation, generative testing, and data definition in such a cohesive way. The ability to compose specs and use them across different contexts (validation, generation, documentation) is particularly powerful. Another impressive thing is that you can easily share the logic between different runtimes - same specs can used in both - JVM and Javascript, which surprisingly difficult to achieve even when writing in Node with TS/JS - you cannot easily share the same validation logic between backend and the browser, even for the same javascript runtime, using its native language. Clojure lets you do that with ease. For everything, there's a trade-off. Some just accept those trade-offs, build their vision, and launch it into the world; Some waste time, lamenting that reality doesn't align with their ideals.
- pjmlp 2y agoIt is my favourite language on the JVM, when not using Java. Because of being a Lisp based language, bringing something else to the table besides "lets replace Java", and the community being welcoming of the host environments where Clojure is a guest.
- iLemming 2y agoClojure is not only on JVM. I often use babashka for shell-scripting and nbb for tinkering on node.js. There's ClojureDart if you like Flutter. For Lua, there's Fennel which is not Clojure but has similar syntax and inspiration. There's Clojerl for Erlang, and glojure, joker and let-go for Golang. There's clj-python and clojure-rs. There's jank-lang (which is not production-ready yet, but already is very promising).
- iLemming 2y ago> Clojure is a lot of fun to tinker with, but man… I love my static types. Static types are great, but boy... I love my REPL. I think I'd hate to actually work while writing code. REPL-driven interactivity with Clojure allows me to treat the work like I'm playing a video game.
- akkad33 2y agoBut statically typed languages like ocaml, haskll, fsharp have repls too
- iLemming 2y agoHave you actually ever used a Lisp REPL? Lisp REPLs are different. If you get down to some pedantic level, every stage there - Read, Eval, Print and Loop, are all slightly different. Compared to a simplest, most basic Lisp REPL, any sophisticated REPL in non-lispy languages simply feels like an interactive shell, nothing more. so called "REPL-driven development technique" allows you to build programs interactively - as I said: "it feels like playing a video game" - you can send any expression or sub-expression, almost without any preceding ceremony, directly from your editor, where you'd be typing code. You evaluate things, sending them to a REPL instance. That REPL instance might be in your browser, or some pod in a kubernetes cluster, or a spaceship, million miles away - that's not some science fiction: https://www.corecursive.com/lisp-in-space-with-ron-garret https://www.corecursive.com/lisp-in-space-with-ron-garret Homoiconicity allows code structures to be treated as what they are, and sending them to the REPL without any kind of transformation. So, yes, while technically, those langs also have REPLs, in reality, writing code doesn't really give you the same kind of feeling.