9 ms·
Clojure is the most enjoyable language I've ever used and I love the interactive development. I haven't written code in any other language that even comes close
by YuukiRey 5y ago
Clojure is the most enjoyable language I've ever used and I love the interactive development. I haven't written code in any other language that even comes close.
Unfortunately I am too lazy and careless to use Clojure in any serious capacity though. I really need a Haskell or Rust compiler to remind me of all my silly mistakes. I can't be trusted to get to the same level of confidence through unit tests or linting or spec or malli.
I actually tried using Clojure for a little web project (~3k LoC in both Haskell and Clojure) and any refactor that included maps that were passed around in many different routes (think authorization, logging, DB) ended up with me chasing down rather weird type errors. Basically the equivalent of can't call X on undefined in JS.
In Haskell I spent at least the same amount of time trying to make various monads play nicely together. Think using IO, Maybe, Either, etc. in the same route handler without having your code be indented ever further to the right.
But the difference is that once I've invested that time I'm very confident in my code, whereas in Clojure there's always this lingering sense of impending doom because surely there's some code path that I haven't thought about.
- draklor40 5y agoI wrote Clojure for 4 years. I understand your pain points. Dynamic types are an impediment beyond a certain code size.
- praptak 5y agoMy experience with dynamic types comes from developing in Python. The projects I've seen were pretty healthy until a large enough part of the original team went away. Once enough people had to figure out their way by experimenting, the projects got messy. I'm not saying this doesn't happen in Java or Go but the static typing enables easier code exploration. That makes code bases more readable to newcomers.
- kaliszad 5y agoThat might be true but good variable names and some terse but to the point comments are much more helpful in my experience.
- neutronicus 5y agoI always really missed header files when working on Python. A sort of enforced distinction between (typed) interface and implementation. In practice I always wound up needing to read code of functions to see what types (or keyword argument!) they expected.
- yogthos 5y agoMy team structures projects by breaking things up into small isolated components that can be reasoned about independently. We'll often do it at the level of namespaces, where a namespace will describe a particular workflow or data transformation, and namespaces tend to be 500 lines or less. It's a similar idea to microservice architecture without the overhead of having to actually split the application up into separate processes. We'll also often pull out code into libraries when we notice it being generally useful outside the original use case. I find that there are a lot of benefits to structuring your code using small components instead of monolithically as tt's easier to reason about and reuse. Any large application can, and in my opinion, should be broken down into small parts that are then composed.
- draklor40 5y ago``` breaking things up into small isolated components that can be reasoned about independently ``` - This is insufficient for the same reasons unit tests are not enough and you also need integration tests. The moment you cross namespace boundaries, you will end up not setting keys /entries in maps , missing logic etc and end up needing something like Spec/Schema....
- mumblemumble 5y ago> you will end up not setting keys /entries in maps This is what I run into. But that's not exactly a dynamic language problem. It's more of a data-oriented programming (in the Clojure sense) problem. I'm coming to the opinion that data-oriented programming techniques only make sense inside a fairly tightly bounded context. One that's small enough that you can see and understand the whole thing at once. As soon as you've got ad-hoc data structures crossing logical boundaries, you lose the ability to keep track of it all, and it becomes very difficult to ensure everyone's interacting with these ad-hoc types in a compatible manner. Incidentally, this is also exactly why I dislike JSON for APIs. I'd much rather share data across boundaries using data structures with explicit, nominal, static types. Like what you get in gRPC. Anyway, my Clojure experience is limited, but I think this is why I have an easier time letting Python code get big than I did Clojure. With Python, I've got myself into some problems with data oriented programming, too. But with Python, it was easy (and idiomatic) to walk that back and switch to using dataclasses.
- jyriand 5y agoIs there something for clojure that TypeScript is for JavaScript?
- tsm 5y agoClojure has an optional type system: https://github.com/clojure/core.typed https://github.com/clojure/core.typed (note that Lisp is flexible enough that a type system can be written as a library) There's also this for run-time checking (i.e., you won't get any help at compile-time, but functions called with bad values will fail in very clear ways): https://github.com/plumatic/schema https://github.com/plumatic/schema
- jeofken 5y agoHopeful new lispers beware that core.typed is not so easy to use as TypeScript, and slows things down quite a bit, and you will need to write a lot of type definitions for 3rd party code. Most (all I’ve seen) clojure shops instead rely on runtime type checking with Spec or Schema, both still popular
- lvh 5y agoThere are a few such things. The most comparison is core.typed, which literally just adds static (gradual) typing to Clojure. That said, I think that's a misleading comparison, and less useful than it is for TypeScript. If you're willing to relax the definition to "static analyzers that find similar issues to what TypeScript might find", there are a few that require very little input (but are less powerful in finding these types of bugs, more powerful in finding others) like e.g. clj-kondo or spectrum. One fundamental challenge is that the idea that you should mostly just pass data around, ideally maps, and those maps should be open for extension, is very core to Clojure. You get a lot of benefits from that, but the idea that anyone can add stuff and that's OK also makes it difficult to detect that some accesses are _not_ OK.
- vindarel 5y agoThis looks like something Common Lisp does better, however I have too little Clojure experience to compare. CL (and SBCL in particular) does "good enough" static type checks, it throws warning at compile time (when we compile one function with a keystroke). We can also precise our function types gradually. It isn't a HM type system (Coalton[1] could be it) but it's already great (compared to no compile-time types at all). Oh, about interactive development: that's sure, CL shines here. Objects get updated (lazily) after a class change, we can install Quicklisp libraries without restarting the image, etc. It's very smooth. 1: https://github.com/stylewarning/coalton https://github.com/stylewarning/coalton
- dmos62 5y agoWhat helps me with this inherent uncertainty is writing in a functional style (simple inputs and outputs, minimal functions) and using runtime tests to check for correctness. The key is to have the tests as far down the abstraction tree as possible. There's a performance hit, but you can use macros to keep it out of production.
- slifin 5y ago- Clojure without clj-kondo - PHP without phpstan/psalm/phan All bad ideas, modern tooling can detect errors in your code before you finish your code expression, but you do need to go out of your way to setup it up Most people do not and then complain about types instead of talking about static analysis If you are ever coding Clojure you should be following this: https://github.com/clj-kondo/clj-kondo/blob/master/doc/editor-integration.md https://github.com/clj-kondo/clj-kondo/blob/master/doc/edito...
- YuukiRey 5y agoI use kondo already, and while it's a nice tool it doesn't solve my problems.
- slifin 5y agoCan you be more specific? There is a primitive type annotation checking system in there, kinds of mistakes not being caught will likely be of interest to Borkdude and others
- YuukiRey 5y agoThe features of the Rust and Haskell type systems that are most useful to me are requiring me to handle every possible null value and modeling branching control flow with algebraic data types, including exhaustiveness checks for those. A value of type "User" can be either "LoggedInUser", "Admin" or "NotLoggedIn", and each of those can have data attached to it. Now I can write functions that only work on specific versions of this and the compiler will remind me to handle every possible variation. If I've narrowed it down to "this function only takes Admin", then the compiler will prevent me from passing the wrong type to it. It's little things like being able to wrap a "String" in a so-called "newtype": "newtype SpecialString = SpecialString String". Now a function can happily accept three string arguments, but if each one is wrapped in a special type you can't mix them up. Or saying something is a "NonEmptyList" and now I can rest assured that it'll always have values. If any of these can be achieved reliably (<-- important!) in Clojure then I apologize for my lack of knowledge!
- 5y ago
- dkersten 5y agoI've been using Clojure since the summer of 2009, started a startup with it, using it exclusively on one project right now... and I agree about static types. I love many many things about Clojure, its my favourite language to use and I find it very well designed over all. But proper first class static types are the one thing I wish it had. My dream programming language is basically a statically typed Clojure. I've on and off poked at trying to make something like that (parsed using instaparse, type checked in Clojure, compiled to C++ using https://github.com/arximboldi/immer https://github.com/arximboldi/immer for the data structures), but haven't had the time to really get anywhere with it. Plus, even if I succeeded, I wouldn't have the rich Clojure (and by extension, Java and Javascript) ecosystem.
- deleted 5y ago[deleted]
- metasoarous 5y agoDo you know about the Typed Clojure project? More or less Racket's contract system, for Clojure: https://github.com/clojure/core.typed https://github.com/clojure/core.typed To me, it's one of the great testaments to the power of Lisp that you can bolt on a static type system after the fact.
- dkersten 5y agoYes, but it’s really not the same as first class static typing support. It also doesn’t help when none of the libraries you use have type annotations and when I last looked it was also quite slow.
- Siira 5y agoCheck out Julia; It has built-in optional static typing, a fast JIT, it can be run interactively in the same style as Clojure, and it supports lisp-like macros.
- SatvikBeri 5y agoI use & love Julia, but it doesn't have really static typing – it has runtime validation based on type annotations. For static typing to be useful, IMO, you need static typechecking, usually done at compile-time.
- eigenspace 5y agoThis is a common point of confusion for people. Julia has a useful and expressive type system, so people just assume it must be static but it's not, julia is dynamically typed. It's intermediate representation is statically typed, but the language itself has dynamic semantics. That said, we have almost every trick and tool up our sleeves that static languages have (in various stages of development). Check out https://github.com/aviatesk/JET.jl https://github.com/aviatesk/JET.jl if you want static typechecking of julia code.
- xfer 5y agoI also like the racket contract system, it is very expressible. But comes with a performance penalty that i can't justify for anything inside a loop. Typed-racket might help but i haven't used much so not so sure.
- void_mint 5y ago> Unfortunately I am too lazy and careless to use Clojure in any serious capacity though. I really need a Haskell or Rust compiler to remind me of all my silly mistakes. I can't be trusted to get to the same level of confidence through unit tests or linting or spec or malli. I think most developers cannot be trusted to make these mistakes, I wouldn't call it "careless or lazy". Languages exist with tools baked into them to prevent slip ups. Why wouldn't you want to take advantage of that tooling? Also, as a result of this comment section, this is the second time in the past week that I've seen the suggestion to use 3rd party typechecking/static analysis tooling in a dynamic language, which (to me) brings up the question: Do you really want a dynamic language, or do you just want the ability to be careless intermittently? There is no shame in saying "I just want to get happy-path code working quickly". There is joy in getting happy path working quickly. I just think, as a codebase in a dynamic language grows, codebases seem to build lots of tooling to make that dynamic language less dynamic.
- fulafel 5y ago> Do you really want a dynamic language, or do you just want the ability to be careless intermittently? [...] I just think, as a codebase in a dynamic language grows, codebases seem to build lots of tooling to make that dynamic language less dynamic. I think this is getting closer to the interesting questions. It's often a good thing that when the product matures and moves more into maintenance mode, you can then incrementally add the kinds of rigidness that suits the needs of that. Clojure has excellent support for these in the schema style data validation tools of spec, malli, etc. If you had a rigid system and slow turnaround from the start, you might not have gotten off the ground to get that far.
- void_mint 5y agoYeah, it seems like programming language decision is, at least in part, a proxy for "At what point do you want to be held accountable for your program's correctness at runtime?" Haskell/Rust - Immediately Java/C#/Go - Relatively quickly Dynamic Languages - Whenever you decide it becomes important
- Scarbutt 5y agoHaskell or Rust is too far the spectrum ;), TS or Kotlin are a nice sweet spot.