Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Coalton is an efficient, statically typed Lisp with ideas from Haskell and OCaml (coalton-lang.github.io)
181 points by b-man 1 day ago | hide | past | favorite | 40 comments
 help



The biggest problem for me is that I can't find a simple way to get started with Coalton.

I'm not a common lisp user, but I want to be. I want to learn common lisp, and I have a fair understanding of types. I think types can benefit the user in understanding more, as well as the inbuilt "intelligence" - (aka: How am I meant to know ahead of time that I can't add these two objects together? Having the editor tell me as I'm writing is a great step)

I have "mine" their text editor, now I just need some tutorials and sample projects to go with it.


In case it helps: https://coalton.app/

And a scripting version: https://smelter.app/


That is a very cool web app with lots of interesting examples and a good place to read code examples and be able to run them. I tried using the editor Mine written in Coalton. Coalton is a nice language project but I can’t get into it because I have been using Common Lisp since 1982, old habbits die hard.

There's a VS Code extension that got me through the Common Lisp learning curve using my familiar tools

https://marketplace.visualstudio.com/items?itemName=rheller....


Recently a new VS Code extension was announced that you may want to check out: https://old.reddit.com/r/lisp/comments/1tn3zff/new_cl_vscode... It purports to be more stable.

I'm one of those people that prefer vscode (actually I'd prefer just about any editor with a UI designed within the last couple of decades over emacs). Lately I've been thinking about working though a nice Lisp book just because the idea appeals to me.

I think learning Common Lisp and learning Coalton are best done separately. For CL the usual reference if you already know how to program is PCL: https://gigamonkeys.com/book/ But the cookbook is invaluable as well: https://lispcookbook.github.io/cl-cookbook/ When it comes to interactive development, looking up resources about SLIME should help since a lot is transferable to the mine environment (or as I prefer, a vim+slimv environment) by having the REPL right there as well as the "beam" metaphor.

For Coalton, I'm still casually exploring it myself. I'm less convinced by the main value propositions (I really like my dynamic typing and CLOS) but I still think it's an interesting language, and being on top of CL means I can mix using it where it makes more sense (even in the same file if I want) without having to abandon CL. I assume you've found the whirlwind tour/awesome-coalton examples for it.. I've seen some usage of it as a way to write "normal" mostly procedural-ish code but with declared types that you know will be checked and used for optimization, so it's sort of like writing PHP with types, or TypeScript, or even in some ways Java or C. e.g.:

    (declare add-two-ints (Integer * Integer -> Integer))
    (define (add-two-ints a b)
      (+ a b))
And (add-two-ints 3.5 5) will type-mismatch.

But I think Coalton's really meant to support writing programs in the style of statically typed languages like Haskell, Ocaml, and F#. Those languages are more than just the above add... style of declaring types, they're about using algebraic data types to model the problem and design your program in those terms. So I'd suggest finding a book or interesting tutorial or sample project in one of those languages, and seeing if you can figure out how to translate it into Coalton, because the Coalton material is still pretty sparse. I've had some success at this by reading "Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#". It really starts at the basics that I think Coalton sort of assumes you understand already. I'll share the main basic things I've taken from it, though I invite any correction.

First, while in many languages we love to pass variables as naked ints and other native types around, making more use of the type system means you can make explicit domain types for these things. You could write something like (define-type-alias CustomerID Integer), and use CustomerID as a type in other types and function definitions. The downside is you don't get a nice constructor for it, and you can accidentally pass CustomerIDs to functions written as only expecting Integers. You can instead write (define-type CustomerID (CustomerID Integer)) and now you'll get a nice little constructor and type errors trying to pass these objects to functions expecting Integers. The downside is you'll need to extract any underlying values with pattern matching/destructuring in match expressions, function arguments, or flattened let expressions.

define-type can be used to model a single thing (like an Integer) but it can also express an "or" relationship, or a "sum type", where a value can be one of several things. e.g.:

    (define-type PaymentMethod
      (CreditCard CreditCardInfo)
      (PayPal     Email)
      (Check      CheckNumber)
      StoreCredit
      Cash)
You might construct a value with something like (Paypal (Email "string@example")), and pass along this object (which is a PaymentMethod) to a function taking a PaymentMethod and doing something to it. You would use the (match ...) syntax to handle the various cases and extract out sub-data as needed. (Note there's none for StoreCredit, it by itself is all the info you need to make a match choice. You could just as well simplify the other two options and look up data elsewhere. In another language you might use enums for this. Having data directly there can be nifty though. e.g. in a state machine with an OrderStatus that's either Unpaid, Paid, Shipped, or Cancelled, you might carry along a Reason string (or richer type) for a Cancelled status...)

define-struct can be used to model "and" relationships, or "product types", for bags of data where all fields are meant to exist at once. e.g.

    (define-struct Widget 
      (name  String)
      (sku   String)
      (price Cents))
Values are constructed like (Widget "Anvil" "1234" (Cents (* 50 100))) and individual data can be pulled out with accessors like (.name my-anvil).

Lastly you have type classes, which can be used for polymorphism in an interfaces sense. For a contrived example you might want to write a generic calculate-total-tax function that works for either Widgets (taxable) or GiftCards (not taxable). The type signature could be: (declare calculate-total-tax ((Taxable :a) => :a * Fraction -> Cents)). That is, it takes some taxable item, and a tax-rate (being lazy with a simple Fraction), and returns a Cents value. That Taxable is a type class constraint, which you define with define-class, along with any functions that make up the complete interface, such as: (get-taxable-amount (:a -> Cents)). You then use define-instance to write a get-taxable-amount function for Widgets (returning a full price), and another for GiftCards (returning 0), and so on if another type comes along. You can also extend built-in type classes this way to support your own new types.


have you tried just using emacs? They have an emacs mode https://github.com/coalton-lang/coalton-labs

Ask an llm how to setup.

This is one area where they excel at with no caveats.


How about not? If an LLM is needed to get set up either the language or the documentation is garbage. Probably both.

If you get deeply excited about config files, sure, go right ahead and set up the dev env without help.

pacman -S git vim gcc make gtest

That is about it. No stinkin' config files needed. Initial Makefile is about 15 lines long, I can type it myself or copy something from another project.


Ah you should have just said you use arch.

But it's a very weak programmer that needs a test suite. Real ones prove the consistency of their programs with goose feather and vellum.


I am not sure about the goose feather and the vellum but you do have a point about proving consistency. Fortunately, I also have a coq/rocq project for that kind of stuff so maybe one day I can be a not-so-very-weak programmer.


So, Greenspun's Tenth Rule seems to have come full circle: now, "Any sufficiently complicated Coalton program contains an ad hoc, slow implementation of half of OCaml." ;)

I've left out "informally specified, bug-ridden" because I guess that's not the case for Coalton, but kept "slow" for when Coalton is used on a slower CL implementation.


There's also Armstrong's Corollary: "All sufficiently complicated distributed systems contain ad hoc, informally-specified, bug-ridden, slow implementation of half of Erlang".

It remains to be seen how Coalton fares there :)


So is there something like IO-Monad in Coalton?

I think that's the greatest feature of Haskell. Divide every program into two parts, one that can have side-effects and one that can not.


That sounds nice in theory, but real programs have multiple channels of IO going on: std IO, logging, network, database, file system. I follow discussions in Haskel groups sometimes, and combining and untangling multiple monads is a persistent problem that doesn't have a good solution yet.

It's debatable how much one is willing to describe historical solutions like MTL style to be a "good" solution to this problem (I explained the downsides in my talk "A History of Effect Systems"[1] at Zurihac 2025), but I certainly have no hesitation in describing my capability-based effect system Bluefin[2], as a good solution.

For the channels you describe above the type signature would look like this

    Yield String e1 -> -- stdout
    Await String e2 -> -- stdin
    Yield String e3 -> -- logging
    Network e4 -> -- network
    Database e5 -> -- database
    FileSystem e6 -> -- file system
    Eff es r
Six different capabilities that give you access to six different channels of IO. A "Yield" to which you yield strings to stdout, an "Await" from which you can await strings from stdin, a "Yield" to which you yield log messages, and three abstract effects that allow you to do network, database and file system operations (abstract because I'm not sure exactly how you want them defined, but there's an example of the implementation of a file system effect at https://hackage-content.haskell.org/package/bluefin-0.6.0.0/...).

No problem with combining or untangling monads whatsoever. This is the natural conclusion of galaxyLogic's point: instead of dividing your program into two parts (IO and not IO) you divide it into six parts (or maybe 2^6 parts?) according to which fine grained effects you have in scope.

If you (or anyone) has any questions about Bluefin I'm happy to help. Please feel free to ask here or on the issue tracker[3].

[1] https://www.youtube.com/watch?v=RsTuy1jXQ6Y

[2] https://hackage.haskell.org/package/bluefin

[3] https://github.com/tomjaguarpaw/bluefin/issues/new


Interesting. Those channels seem to all manage side-effects that persist after program execution. What about state-changes that are made and persist into the memory of the running program? Or is there any need for that?

You should try actually doing some Haskell programming. There are some anti-patterns but the RIO pattern is very coherent and flexible. One IO base monad, one readerT that holds an IORef that gives you access to every other effect you need.


> combining and untangling multiple monads

What? I have to assume you mean combining and untangling multiple effects in an effect system. And the solution is simple: don't. IO is the only monad you use for all IO: logging, networking, databases, and filesystems included. Adding a layer of ReaderT on top is about the most complicated you do. Every other monad is pure. Effect systems are a great way to build abstractions that aren't usually worthwhile. It is the same kind of mentality that leads to AbstractWumbleProviderFactoryBean-oriented programming in Java.


The IO monad is for reifying execution order of a program. You more or less never have multiple IO monads.

Coalton has a monad type class (and applicative etc.) but it is not a pure language, so it has no special annotation for side effects.

No, it wouldn't make sense. Coalton is a strict language.

I'm not a Haskeller but I would like to use a language that indeed separates all code into two areas, one in which I can write non-pure functions and one in which I can not.

That would obviously make it easier to analyze and test and verify the program by first focusing on the pure part of it, then on the impure part. I'm not sure if that is possible in "strict languages" but perhaps.


That is not the only reason to have an IO monad. PureScript and Idris are both strict languages that still reify IO effects. Its still useful to know which functions are pure.

I'm actually not a fan of Idris' implementation of the IO monad, it really should have been a free monad with a cofree comonad so you can enforce invariants against the world without a bunch of extra machinery. It seems kind of important for a dependent type system.

I quite like Mercury's non-monadic linear IO + determinism semantics. You can subtype the world with insts and unify against the world-state pretty easily.


The words "Statically Typed" and "Haskell" made me click on something lisp related for once.

Now that the native ocaml repl has landed, can't we just slap a s-expression syntax on top of ocaml and call it a day? We would have homoiconicity and the macros that go with it, and still could call "(compile `some-code)". Isn't that enough?

I'd love that. The janky OCaml syntax is my least favorite part of the language.

Aside from the fact that slapping an s-expression syntax on top of a language typically leaves you with a fairly crappy Lisp, that would also lack the excellent Common Lisp interop that Coalton has.

OCaml is a better candidate than most. It already has a macro system (PPX), and the core language has a lot in common with Scheme.

The PPX macro system is far from perfect, that's the whole point of this idea: with the native REPL + an s-expression syntax, you have lisp-like macros for free.

Nothing will ever be enough, for all interpretations of the statement, reveling in all the irony, pathos, and prideful triumph that could possibly be extracted from such interpretations from here to eternity

Humans will keep inventing and remixing


We have a deep capacity for remixing.

> native ocaml repl has landed

What does this refer to? Most people use the OCaml `utop` REPL which isn't okay for some printf debugging but nowhere near what a Lisp could do.


There is a native toplevel hidden in OCaml5 source tree (not installed by default). So you can basically enter an expression, and the compiler will turn it into native code and dynamically load it. Interactive REPL with native code was not possible before that (apart from a short lived experiment long ago, if memory serves me well).



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: