A concatenative programming library in 5 lines of Clojure
blog.fogus.me
blog.fogus.me
prog = do {1; 2; add; 3; 4; add; add}
The cool thing is that I get "subroutines" (or, as Forth calls them, "words") for free: incr = do {1; add}
prog = do {1; incr; incr; incr}
The interesting thing is that incr and prog are actually ASTs: I can run them, with a function called runProgram, but I can just as easily pretty print them. This is a wonderful property of free monads that's very useful for real DSLs. And, in fact, I'm using them for a current project--they're great.With a bit of cleverness, it's probably possible to encode some of the stack semantics into the type system, making running a program with too few or too many elements left on the stack a type error. Also, with GADTs, I can probably add support for something like strings and have a mini string/int type system just for my little language, without much effort.
Indeed, Factor does this. In addition to checking for stack (over/under)-flows it's what allows really powerful behavior like the ability to say "run both these words with the current stack" without having to explicitly state how much of the stack to duplicate.
Only allowing two-argument functions doesn’t cut it, and you need some means of returning multiple values as well. You also need a means of abstraction (e.g., Clojure fns) and combinators for application, abstraction, and stack manipulation. A full implementation of an interpreter for a concatenative DSL would be rather longer than this—though still probably in the realm of 100 lines.
With non-variadic arithmetic functions:
(defn add [x]
(cons (+ (first x) (second x)) (rest (rest x))))
Your “postfix” function can remain a simple “reduce”, but can be totally agnostic of function arity. That frees you up to write the basic combinators: (defn dup [x]
(cons (first x) x))
(defn zap [x]
(rest x))
(defn swap [x]
(cons (second x) (cons (first x) (rest (rest x)))))
(defn ap [x]
(apply (first x) (rest x)))
...http://concatenative.org/wiki/view/Om
It uses prefix notation: instead of a data stack, each function takes the remainder of the program for rewriting.
It's difficult to have simpler semantics than SK calculus. Perhaps a more appropriate modifier in this instance would be "more convenient".
https://github.com/brandonbloom/factjor
Factjor supports quotations, currying, composition, and has most of the primary combinators: dip, keep, cleave, spread, application, etc. It's only about 100 lines.
They're a lot of fun, and some simple ideas can lead to really interesting results. Since all code is just a sequence of tokens it becomes trivial to treat words, quotations, and entire programs as lists and manipulate them a la Lisp. Another nice thing is many such languages give you access to the parser. One I wrote recently is (almost) interpreted directly from text and let's you hook into the parsing process. It doesn't have comments, but it's trivial to add them:
IMMEDIATE: /* BEGIN NEXT-TOKEN STRING */ EQ? UNTIL ;
This turns "/*" into a function that will drop tokens until it comes to a terminating marker. Such functionality can be used for macros or to add to DSLs.Also interesting is how the stack changes the way you write code. What you end up with is program that's entirely made of something like pipes in ML-like languages or chaining constructs in JavaScript. They're very easy to compose together (and generally there's less overhead) so it encourages "factoring" out into lots of little pieces; something you can do in almost any language but doesn't feel as natural IMO.
http://code.google.com/p/consize/
It is extensively documented in German — I plan to add some documentation in English as well. But you might enjoy reading the source code anyhow ;-) After reading consize.clj, I recommend to continue with prelude-plain.txt.
BTW: Adding object-orientation (i.e. polymorphism via generic words & multiple inheritance much like Clojure does) is possible in about 30 LOC.
Dominikus