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.
https://marketplace.visualstudio.com/items?itemName=rheller....
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)
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, and another for GiftCards, and so on if another type comes along. You can also extend built-in type classes this way to support your own new types.