Blunt
My day job is working on compilers, and also in my spare time I'm occasionally fiddling with a new language and interpreter for it. It's a strongishly typed concatenative functional language called Blunt.
I'm taken with the idea of point-free programming: it's kinda elegant, yet tacit and often unreadable. I don't expect Blunt to actually be used, but it's fun to make. This means Blunt has no named variables, only definitions.
It's employing a novel (I think) mechanism for function application, allowing pairs of values to be applied as or passed to functions in specific ways. A pair is constructed with a comma operator:
(a, b)
Applying a pair to an argument applies both members in parallel:
(f, g) x => (f x, g x)
Using the 'at' operator applies a function to a pair as two arguments and the caret operator lifts a function into a pair:
f @ (x, y) => f x y
f ^ (x, y) => (f x, f y)
And the dot operator composes functions, while the left-pipe operator applies them:
(f . g) x => f (g x)
f . g <| x => f (g x)
So functions are built with composition and a complete lack of named parameters. But it's been proved that valid and useful programs can be built with combinators like these, along with id
which is a function that returns its argument, and const
which returns its first argument, discarding its second. This is especially evident in the SKI calculus. Also flip
which will reverse the parameter order for a function.
Here's an expression which will print the string 1/2
:
putsln <| (join-str . flip join-str "/") @ (to-str ^ (1, 2));
Mentioning the SKI calculus had me thinking whether Blunt needed an S combinator in its library, since it has K (const
) and I (id
) already. But I found that S could be implemented with @
and composition. In SKI calculus:
S f g x => f x (g x)
And in Blunt:
((f @) . (id, g)) x
=> (f @) . (id x, g x)
=> f @ (x, g x)
=> f x (g x)
This made me quite happy.