(or, welcome to the series ...)
The Premise
I've mentioned "why Coalton" here so won't repeat all of it.
It blends two traditions:
- Lisp’s expressive power, incremental development, and rich ecosystem
- Haskell’s clarity, predictability, and type discipline
Given that it's now possible to easily run programs in the terminals as scripts, I thought of sharing series of snippets over time, trying to use the conceptual structure of Haskell to teach Coalton ... not because Coalton is Haskell, but because the underlying mental models (pure functions, algebraic data types, pattern matching) translate cleanly!
A First Glimpse: Catching Mistakes Early
Many tutorials begin with a trivial arithmetic example, and we might as well do the same here (though they show syntax, and we'll show safety).
#!/usr/bin/env smt run directive at the top, and then write out the rest. Larger Coalton programs would be part of a codebase, linked in to it, and require (coalton-toplevel ...) enclosing ... but the focus here is on small, standalone scripts.Create a file named intro.smt:
#!/usr/bin/env smt run
;; A simple function expecting a Number
(declare double (Integer -> Integer))
(define (double x)
(* 2 x))
(define main
(println (show (double 21))))Run it:
$ smelter run intro.smt
42Everything works.
Now change it to be as follows:
#!/usr/bin/env smt run
;; A simple function expecting a Number
(declare double (Integer -> Integer))
(define (double x)
(* 2 x))
;; This will cause a type error!
(define main
(double "Hello"))Run again.
You should see an error similar to:
Error running script: error: Type mismatch
--> <macroexpansion>:7:12
|
7 | (DOUBLE "Hello")))
| ^^^^^^^ Expected type 'INTEGER' but got 'STRING'
If you come from dynamic Lisp, this is the moment the Coalton value proposition clicks:
A whole category of bugs evaporates before runtime.
A More Meaningful Example: A Custom Optional Type
Optional type as this example shows, you'd use the built-in one (Coalton ships with Optional, Some, and None in its standard library). The only reason we're we're just trying to avoid name collisions and make a point.Here's simple-monad.smt:
#!/usr/bin/env smt run
;; Our own "MyOptional" type for demonstration
(define-type (MyOptional :a)
(MyNone)
(MySome :a))Let's extend it to have a dummy lookup and show (to illustrate the point here):
;; Safe lookup - returns None if key not found
(declare safe-lookup (Integer -> (MyOptional Integer)))
(define (safe-lookup id)
(if (== id 1)
(MySome 100)
(if (== id 2)
(MySome 200)
MyNone)))
;; Simple show for MyOptional
(declare show-optional ((MyOptional Integer) -> String))
(define (show-optional opt)
(match opt
((MyNone) "None")
((MySome val) (show val))))Then, we can use it in the following way:
(define main
(progn
(println "safe-lookup 1 =>")
(println (show-optional (safe-lookup 1)))
(println "safe-lookup 99 =>")
(println (show-optional (safe-lookup 99)))))Run:
$ smt run simple-monad.smt
safe-lookup 1 =>
100
safe-lookup 99 =>
NoneWhy this example matters
- You cannot return the wrong shape of data.
- Consumers must handle both Some and None.
- The compiler enforces correctness.
- There is no nil ambiguity or silent failure path.
This is the core benefit Coalton brings to Lisp: structured correctness with minimal friction.
"Haskell Concepts"
These aren't really unique to Haskell, and none of this requires reading Haskell ... you don't need to know the tooling, syntax or be familiar at all, to be able to read and appreciate Coalton.
However, while we can ignore its syntax, many Haskell books do have a good conceptual organization, and we can follow that here:
- pure functions
- recursion as structure
- algebraic data types
- pattern matching
- type inference
- type classes
- functorial patterns
These ideas form a clean and time-tested way to learn functional programming. ... and Coalton implements them in a Lisp environment!
What this series will cover*
Each post pairs a core functional programming idea with its Coalton implementation:
- Functions: purity, signatures, evaluation
- Lists & Recursion: structural computation
- Pattern Matching: safe deconstruction
- Higher-Order Functions: shaping behavior
- Types & Type Inference: precision
- Type Classes: polymorphic structure
- Algebraic Data Types: structured modeling
- Functors, Applicatives, Monads: predictable effects
- Interop: using Coalton with real Lisp code
- A Small Project: putting everything together
(* most likely, over many months, as and when I get time, varying with what else I want to do/make/write, might decide to skip some of this, usual disclaimers about hobbyist efforts ...)
Each post is self-contained and runnable.
Try It Yourself (Smelter Ready)
Let’s add a safe “head” function using Optional.
Creating safe-list-head-example.smt:
#!/usr/bin/env smt run
;; Safe head function using Optional from standard library
(define (safe-head lst)
(match lst
((Cons x _) (Some x))
(_ None)))
;; Helper to show Optional results
(define (show-optional opt)
(match opt
((Some val) (show val))
(_ "None")))
(define main
(progn
(println "safe-head [1 2 3] =>")
(println (show-optional (safe-head (make-list 1 2 3))))
(println "safe-head [] =>")
(println (show-optional (safe-head Nil)))))Run it:
$ smt run safe-list-head.smt
safe-head [1 2 3] =>
1
safe-head [] =>
None(As smarter people than me have said) Coalton isn’t trying to replace Lisp, it’s strengthening it!
Stay tuned for more ...