PureScript / Haskell-syntax AffineScript. Write code that looks like PureScript or Haskell, get affine resource guarantees and typed-wasm output.
LucidScript is
AffineScript with its
lucid face pre-selected. If you write PureScript or Haskell, you
already know most of the syntax: module Foo where, name signatures, equation-style
Typef x = … definitions,
x → … lambdas, and -- comments. The compiler checks that
your resources (files, sockets, tokens, handles) are used exactly as
many times as you declare — and proves it at compile time. No null
pointer exceptions. No use-after-free. No silent data races. No GC
overhead.
This repo is a brand surface only. The compiler, type checker, borrow checker, and codegen all live in affinescript. This repo carries:
-
Examples idiomatic to PureScript / Haskell developers
-
Documentation aimed at the typed-functional-programming community
-
A
lucidshim CLI that aliasesaffinescript--facelucid -
Tutorial and migration guides for bringing a typed-FP mental model to a strongly-typed, affine-typed, WASM-targeting world
examples/hello.affine:
-- face: lucidscript
module Hello where
effect IO {
fn println(s: String) -> ();
}
main :: -{IO}-> ()
main () = println("Hello, LucidScript!")module … where, name signatures, equation-style definitions,
Type-- comments — all
lower to canonical AffineScript and produce the same typed-wasm output
as every other face.
opam install affinescript
git clone https://github.com/hyperpolymath/lucidscript
cd lucidscriptThe affinescript binary does the work. The bin/lucid shim in
this repo just defaults the --face flag.
# Direct, via affinescript:
affinescript eval --face lucid examples/hello.affine
affinescript compile --face lucid examples/hello.affine -o hello.wasm
# Or via the lucid shim (same thing):
./bin/lucid eval examples/hello.affine
./bin/lucid compile examples/hello.affine -o hello.wasm
# Or via the justfile:
just run examples/hello.affine
just preview examples/hello.affine # show the canonical loweringSource files use the canonical .affine extension. The face is
selected by the -- face: lucidscript pragma on the first
comment line, or by the --face lucid flag.
LucidScript is one of six established faces over the AffineScript core:
-
AffineScript — the canonical face
-
RattleScript — Python-style
-
JaffaScript — JavaScript / TypeScript-style
-
LucidScript — PureScript / Haskell-style (this repo)
-
CafeScripto — CoffeeScript-style
-
PseudoScript — pseudocode-style
All six share the canonical .affine extension and lower to the same
AST. Errors are reported in face-appropriate vocabulary.
PureScript and Haskell give functional programmers a genuinely sound
static type system — algebraic data types, type classes, row
polymorphism, and effect tracking that catch whole classes of bugs
before runtime. What they don’t give you is ownership and affine
resource control, or a portable native compilation story: resource
leaks, double-frees, and “did I close that handle?” are still managed
by convention and discipline, and getting compiled output into a browser
or a WASI runtime means a separate toolchain. LucidScript is a
typed-FP-shaped on-ramp to a language that adds all of those — without
forcing you to abandon module … where, type signatures, or
equation-style definitions.
The semantic core is already well aligned with PureScript: row polymorphism, ADTs, extensible effects, and type classes are native to AffineScript, not bolted on. For a PureScript / Haskell developer migrating in, the steps are:
-
Rename
.purs/.hsfiles to.affineand add--face:lucidscriptat the top.2. Keep your
namesignatures and equation-style
Typefx=…definitions — the face reads them directly. -
Write function application with canonical parens —
f(x)rather than Haskell curryingfx(a deliberate constraint to keep the lowering text-based). -
Use the explicit forms (
case…of,let…in…) where multi-clause definitions,do-notation, orwhere-hoisting aren’t yet handled (see Limitations). -
Compile to typed-wasm; the output runs in browsers, Node, Deno, Wasmtime, and any WASI runtime.
Some PureScript / Haskell surface features need AST-level rewrites that the text-to-text face transformer doesn’t yet handle:
-
Multi-clause function definitions (
fact0=1;factn=n*fact(n-1)) -
do-notation desugaring beyond the trivial form -
where-block hoisting
These are tracked in
the
affinescript faces README under “Known transformer gaps”. Until they
land, write the explicit forms (case … of, let …
in … instead of where).
Alpha. The face transformer is implemented in
affinescript/lib/lucid_face.ml. Known limitations are tracked in
the
affinescript faces README under “Known transformer gaps”.