Here are some examples under:
(declaim (optimize (speed 2)))
First example is a generic multiplication. x and y could be _any_ type at all. (defun fn (x y) (* x y))
If we disassemble this function, we get the following: ; disassembly for FN
; Size: 34 bytes. Origin: #x1001868692 ; FN
; 92: 488975F8 MOV [RBP-8], RSI
; 96: 4C8945F0 MOV [RBP-16], R8
; 9A: 498BD0 MOV RDX, R8
; 9D: 488BFE MOV RDI, RSI
; A0: FF142540061050 CALL [#x50100640] ; SB-VM::GENERIC-*
; A7: 4C8B45F0 MOV R8, [RBP-16]
; AB: 488B75F8 MOV RSI, [RBP-8]
; AF: C9 LEAVE
; B0: F8 CLC
; B1: C3 RET
; B2: CC0F INT3 15 ; Invalid argument count trap
Note that it calls `GENERIC-*` which probably checks a lot of things and has a decent overhead.Now, if we tell it that x and y are bytes, it's going to give us much simpler code.
(declaim (ftype (function ((unsigned-byte 8) (unsigned-byte 8)) (unsigned-byte 16)) fn-t))
(defun fn-t (x y) (* x y))
The resulting code uses the imul instruction. ; disassembly for FN-T
; Size: 15 bytes. Origin: #x1001868726 ; FN-T
; 26: 498BD0 MOV RDX, R8
; 29: 48D1FA SAR RDX, 1
; 2C: 480FAFD7 IMUL RDX, RDI
; 30: C9 LEAVE
; 31: F8 CLC
; 32: C3 RET
; 33: CC0F INT3 15 ; Invalid argument count trap* (fn-t "hello" "world")
But a good rule-of-thumb is that these compile-time type errors are more of a courtesy, rather than a guarantee. As soon as you abstract over fn-t with another function, like so: (defun g (x y)
(fn-t x y))
and proceed to use g in your code, all the static checking won't happen anymore, because as far as g is concerned, it can take any input argument types. CL-USER> (defun will-it-type-error? ()
(g "x" "y"))
;; compilation WILL-IT-TYPE-ERROR? successful
No compile-time warning is issued. Contrast with Coalton: COALTON-USER> (coalton-toplevel
(declare fn-t (U8 -> U8 -> U8))
(define (fn-t x y)
(* x y))
(define (g x y)
(fn-t x y))
(define (will-it-type-error?)
(g "hello" "world")))
error: Type mismatch
--> <macroexpansion>:8:7
|
8 | (G "hello" "world")))
| ^^^^^^^ Expected type 'U8' but got 'STRING'
[Condition of type COALTON-IMPL/TYPECHECKER/BASE:TC-ERROR]1. Bringing abstractions that are only possible with static types, like ad hoc polymorphism via type classes. For example, type classes allow polymorphism on the return type rather than the argument types. Something like
(declare stringify (Into :a String => :a -> :a -> String))
(define (stringify a b)
(str:concat (into a) (into b)))
; COALTON-USER> (coalton (stringify 1 2))
; "12"
The function `into` is not possible in a typical dynamically typed language, at least if we aim for the language to be efficient. It only takes one argument, but what it does depends on what it's expected to return. Here, it's expected to return a string, so it knows to convert the argument type to a string (should knowledge of how to do that be known by the compiler). Common Lisp's closest equivalents would be (concatenate 'string (coerce a 'string) (coerce b 'string))
which, incidentally, won't actually do what we want.2. Making high performance more accessible. It's possible to get very high performance out of Common Lisp, but it usually leads to creating difficult or inextensible abstractions. A lot of very high performance Common Lisp code ends up effectively looking like monomorphic imperative code; it's the most practical way to coax the compiler into producing efficient assembly.
Coalton, though, has an optimizing compiler that does (some amount of) heuristic inlining, representation selection, stack allocation, constant folding, call-site optimization, code motion, etc. Common Lisp often can't do certain optimizations because the language must respect the standard, which allows things to be redefined at run-time, for example. Coalton's delineation of "development" and "release" modes gives the programmer the option to say "I'm done!" and let the compiler rip through the code and optimize it.
3. Type safety, of course, in the spirit of ML/Haskell/etc.
(deftype Proper-List-Of (subtype)
`(or Null
(Cons ,subtype
(Proper-List-Of ,subtype))))
Doesn't work (for example). There kind of are ways to work around it to some extent with satisfies and ad-hoc predicate generation, but Coalton is a true value add in that aspect.Coalton moves that to the compilation step so you get an error back the instant you send the form to the REPL.
It's 2025, people. Dynamic languages for serious projects were a 90s fad, hopefully never to be repeated.
I'm completely clueless about Coalton, (and almost completely an idiot when it comes to CL more generally - been playing for a couple of years at this point but even so, every day is still a school day...)