Concatenative languages lend themselves to rewriting because they do not bind variables and thus do not incur any confusion with renaming/scope (compositional rather than applicative).

Manfred von Thun has already written on this: there are algebraic laws for concatenative programming like the algebraic laws one has for e.g. groups (\(a \cdot 1 = a\)).

Some simple ones that do not need quotation:

I implemented the above lints in my own Kempe compiler. A linting pass is easily implemented with pattern matching one has in ML-style functional languages:

lintAtoms :: [Atom b b] -> Maybe (Warning b) lintAtoms [] = Nothing lintAtoms (a@(Dip l _):a'@Dip{}:_) = Just (DoubleDip l a a') lintAtoms (a@(IntLit l _):(AtBuiltin _ Drop):_) = Just (PushDrop l a) ...