News for hackers. Stuff that matters.
Re:The Lisp curse, now coming to your ecosystem by rmunn on Thursday November 05, 2026 @06:56PM (#50386303)
Since you mentioned the mine editor you must be aware of Coalton, but since you didn't mention Coalton I'll mention it here. Coalton is a Lisp library that basically adds static typing, type inference, and Haskell-style typeclasses on top of Common Lisp. (If you've never encountered type classes, then think of interfaces in the OOP language you know best. Type classes are what interfaces should have been: they can do everything interfaces can, and many things interfaces can't). Here's an example from the Coalton docs, which should be pretty self-explanatory; go read https://coalton-lang.github.io/manual/topics/whirlwind-tour/ if you want to see more.

    (coalton-toplevel
      ;; Type classes are defined with the define-class keyword
      (define-class (Eq :a)
        (== (:a * :a -> Boolean)))

      (define-type Color
        Red
        Green
        Blue)

      ;; Type class instances are defined with the define-instance keyword
      (define-instance (Eq Color)
        (define (== a b)
          (match (Tuple a b)
            ((Tuple (Red) (Red)) True)
            ((Tuple (Blue) (Blue)) True)
            ((Tuple (Green) (Green)) True)
            (_ False))))

      ;; Type declarations can have constraints
      (declare is-eql (Eq :a => (:a * :a -> String)))
      (define (is-eql a b)
        (if (== a b)
          "They are equal"
          "They are not equal"))

      ;; Multiple constraints must be wrapped in parentheses
      (declare double-is-eql ((Eq :a) (Eq :b) => (:a * :a * :b * :b -> String)))
      (define (double-is-eql a b c d)
        (if (and (== a b) (== c d))
          "Both pairs are equal"
          "The pairs are not both equal")))
Re:The Lisp curse, now coming to your ecosystem by rmunn on Thursday November 05, 2026 @10:08PM (#50387675)
For anyone who might not have encountered typeclasses before, the example might not be quite as self-explanatory as I thought it would be, so I'll explain that example a little.

The `(define-class (Eq :a) ...)` block is defining a type class called `Eq` with a type parameter named `:a`. Think of this as an interface that can be added after the fact. Most (all?) major OOP languages only allow you to declare interfaces when you declare a class, but in Haskell, and in Coalton that takes a lot of inspiration from Haskell, you can declare "interfaces" (typeclasses) on any pre-existing type, including ones coming from a library. Here, the `Eq :a` interface means that there must be a function called == (in Lisp even the operators are defined as functions) that takes two parameters of type :a and returns a Boolean.

`(define-type Color ...)` is defining a simple enum type. (You can do a lot more with it, of course, but this example is kept simple). Then `(define-instance (Eq Color) ...)` is saying "The type called Color is an instance of the typeclass (interface) called Eq". And since Eq requires a function called `==`, inside the define-instance block you have to define that function for the Color type. The pattern-matching should be simple enough that I don't think I need to explain it.

Now, the thing that makes this more powerful than interfaces is something I already touched on. Here the Color type is declared and then its typeclass instances are defined right away, which is the normal thing to do when you're creating a new type: keep all its code together. But if that Color type had come from another library that you imported, you would still be able to say "oh, and this Color type does implement Eq: here's the definition of == as applied to Color values". You're not restricted to only the interfaces that the library author thought to apply: you can take someone else's code and add interfaces to it! There have been several times when I really wished that were possible in C#.

Finally, the constraints before the => fat arrow are like declaring your function parameters as `public T isEqual<T>(T a, T b) where T : IEquatable`. This allows Coalton to catch, at compile time, any call to `is-eql` with parameters for which the `==` function is not defined.

There's plenty more at https://coalton-lang.github.io/manual/topics/whirlwind-tour/.

Re:The Lisp curse, now coming to your ecosystem by xedrac on Thursday November 05, 2026 @11:47PM (#50388347)
Thanks for the detailed explanation. I'm very familiar with Haskell, so this all feels very natural. How good is the type inference in practice? Is it a good as Haskell? And are there any places where you'd rather use vanilla common lisp?
Re:The Lisp curse, now coming to your ecosystem by reikonomusha on Friday November 06, 2026 @12:52AM (#50388688)
Inference uses roughly the same algorithm and type system as Haskell (depending on what flavor of it... GHC has tons of extensions). It supports everything in the standard Haskell type system, as well as a few common extensions of Haskell, including multiparameter type classes, functional dependencies, etc.
Re:The Lisp curse, now coming to your ecosystem by nextaccountic on Thursday November 05, 2026 @10:11PM (#50387704)
About static typing and type inference: what does it do with code that it can't infer? It turns into a dynamic type ala Typescript, or it gives a type error?

I mean it must employ gradual typing, right? So that it can interface with existing code that is untyped

Re:The Lisp curse, now coming to your ecosystem by rmunn on Thursday November 05, 2026 @10:34PM (#50387836)
If I've understood https://coalton-lang.github.io/manual/topics/whirlwind-tour/... correctly (and please do read it for yourself because I might be misunderstanding it: I haven't used Coalton yet, only read the manual), Coalton will guess a type in some cases (such as numbers, but not only numbers). I don't know whether it gives warnings or errors for types it can't infer, but since it's based on Common Lisp I would assume it would give a warning and continue with an unknown type.

But there are definite advantages to having Coalton know your types, which is why they give you a `(declare foo Integer)` form. This doesn't compile to any code, it's a compiler hint that tells Coalton that it can assume `foo` is an int, not a float64/double, which allows it to pick at compile time which implementation of + gets used in `(+ foo 5)`.

The 2025 video introducing Coalton at https://www.youtube.com/watch?v=of92m4XNgrM gives an example of what Coalton can do with knowing types. Robert Smith (the presenter, and the primary creator of Coalton) gives an example of a Fast Fourier Transform function that uses generic numeric types so you can call it on any number type: float32, float64, int-modulo-a-prime, whatever. He then benchmarks it, and it takes about 1 second to run 2 million times. Then he adds the single compiler hint `(monomorphize)` above the function declaration. This tells Coalton "Hey, instead of doing a dispatch table lookup in this function and calling different + implementations based on the type of the parameters, I want you to create two different copies of this function, one that will call the specialized float32 math functions, and the other one that will call the specialized float64 math functions." (Or three copies if you also called it with your int-modulo-prime number type). This eliminates a dispatch-table lookup and allows the compiler to produce much more specialized code. It comes at the expense of larger binaries, of course: he's explicit about how this is trading speed for size, so it's an opt-in choice you make (by adding a `(monomorphize)` declaration above the function definition) rather than something that Coalton defaults to.

But I've digressed. Yes, I believe (though note my caveat about not having used Coalton yet) that it would use dynamic types for code whose types it can't infer. But I'm pretty sure it would also give you a warning. If it gives you an error, the solution is to add a `(declare [name] [type])` line in the appropriate place.

Re:The Lisp curse, now coming to your ecosystem by reikonomusha on Thursday November 05, 2026 @11:21PM (#50388147)
Coalton infers and checks all types for pure Coalton code; there's no such thing as a dynamic or untyped expression.

Code that is untyped—Common Lisp code—is run in (lisp ...) forms [0] which requires the user to annotate the type and adds dynamic type checks to it to ensure it meets the contract the programmer specified.

[0] https://coalton-lang.github.io/manual/operators/lisp/