Jux: A (WIP) stack-based concatenative functional programming language

Hey all!

I’d like to talk about this pet project I’ve had for a couple of months now. It is a concatenative programming language, called Jux.

disclaimer: This is very much a Work In Progress. I would like feedback, but do note that it is far from unfinished, as well as a hobby project that I can only devote so much time on right now :wink:

Jux is a concatenative programming language. This means that everything is a command that pushes a new value to the stack, possibly by consuming one or multiple things that were previously on top of the stack. So to add two numbers, one would type 1 2 add (or possibly 1 2 +). First a 1 is pushed, then a 2 is pushed, then the top two elements are popped, and the result of adding them is pushed back to the stack. Thus, everything is written in Postfix (AKA Reverse Polish) Notation. The internals of lists (called ‘quotations’) are not evaluated until a command that consumes it is used. This means that you can manipulate snippets of source code before it is evaluated; code == data == code. (Interestingly, this is an even simpler way of representing than Lisps S-expressions; and no parentheses are needed :stuck_out_tongue: )

A slightly more complex code example would be:

\print_if_even
"
This function looks at the number on top of the stack, 
and prints `x is even!` if it is even, and if it is odd, it prints `x is odd!`.
"
[
   [even?] [to_string "  is even!" concat] [to_string "  is uneven!" concat] ifte print
]
def

42 print_if_even
#=> 42 is even!
51 print_if_even
#=> 51 is uneven!

(This takes the top element, checks if it is an even number, and then prints that number, followed by a message stating if it is even or not.)

What makes Jux different from other concatenative languages, is that it is designed to be as easy to implement on new platforms; The current (WIP) version of Jux needs only 18 trivial primitive functions, to create a standard library with currently nearly two hundred functions. Also, because everything is on a stack, no garbage collection schemes or other complex logic is needed for a Jux interpreter. I’ve been able to implement a basic Jux interpreter in both Elixir and Ruby in < 3 hours each. (Soon I will try the same for Haskell, JavaScript and maybe some other languages as well)

Of course, all these fallback implementations written in Jux itself are a lot slower than if you were to perform them in the host language. So one could make an implementation more efficient by moving more and more commands from the standard library to the host language.

So, that is the basic concept: Make a language whose base is as small as possible, but whose apex (i.e. user-land) is feature-rich.
Jux is mostly meant to be an intermediate language to be compiled to. I am still researching (and I’d love feedback on) the idea of creating a bytecode variant of Jux.

Jux takes a lot of inspiration from Elixir (such as first-class documentation, explicitness is important.), as well as the earlier concatenative languages Joy, Cat and PostScript.

Here are the things I am very unsure about, and I would love to hear feedback from you people, because I know that many of you are involved in many different language - or framework building projects, and come from a large amount of different backgrounds.

  • I wonder how hard it is (or if it is even possible) to build a compiler for Jux, instead of an interpreter.
  • I wonder if it might be possible to (a version of) the Actor Model to the core language, without making the language ridiculously hard to implement. I would really love to do this; bring the Actor Model to a lot more places.
  • I wonder if it might be possible to change the language to be more strongly-typed, which might improve speed and explicitness (without making the language a lot harder to implement).
  • I wonder what a proper way to do FFI(Foreign Function Interface, calling parts of the host language from within Jux) would be.

Well, if you’ve read so far, you’ve made me very happy. I really wanted to share this idea with some other people, because one person can only think in a single way about a problem. I really look forward to any and all comments about this :slight_smile: .

6 Likes

Great to see you announcing this! Been looking forward. :slight_smile:

As for native speed (at least elixir native), you could make an elixir macro that takes the language and compiles it to, say, a function. :slight_smile:

If you could keep it within elixir syntax than that would be nice, but I do not see that being possible, thus taking a string argument, parsing your language from it into raw elixir AST and returning it would be a nice method. :wink:

defmodule Blah do
  use Juxex
  def callTheJux(i) do
    ~j"""
    ^i 42 +
    """
  end
end

iex> Blah.callTheJux(42)
84

Or so if the pin operator let you bring in an external variable from the ‘environment’. ^.^

1 Like

Thank you for your reply, OvermindDL1! Great to see you around, and again, thanks for your earlie input :smiley: !

Keeping the language a subset of Elixir’s AST is not going to work, because there are some things that are sheer incompatible; the first one being that the list syntax does not use , between elements in Jux.

In your example above, you could simply use string interpolation #{i} instead, and it would already work in Elixir :smile:.

What might be harder with the FFI, is how to pass to the host-language, variables (i.e. things from the stack) from inside Jux. Hmm… maybe some backwards string-interpolation could be used, i.e. using backticks, and a syntax like $0, $1, etc to point to consecutive elements on the stack, counting from the top.


With writing a compiler, the main problems I have are these:

  1. How to compile to a function that takes one or multiple elements from the top of the stack as input?
  2. What happens if someone defines a new function at runtime? Will a compiled program then basically need the compiler in its binary as well?

[quote=“Qqwy, post:3, topic:1755”]
How to compile to a function that takes one or multiple elements from the top of the stack as input?[/quote]
Just have it be done inline, make this a macro call that transforms the string into elixir ast:

defmodule Blah do
  use Juxex
  def callTheJux(i) do
    juxToElixir [i 3] """
    4 + *
    """
  end
end

iex> Blah.callTheJux(2)
20

Just let the initial stack be passed in as the first call to the macro, the second being the body that gets compiled to elixir ast and let it run through it and return whatever is left on the stack? :slight_smile:

I’d say keep it fully self-contained in the block of where it is defined.

You are completely right! I was severely overthinking it. The cleanest way might just be to pass the stack as a parameter, and to receive the new stack as return value. Then the only way of FFI is to pass a single function name per FFI-statement, which the interpreter will recognize and apply the stack to as argument. But I think this is a whole lot cleaner than having multiple interleaved statements just pasted in-between normal code. And it also is probably a lot easier to implement.

Another thing I am thinking about is how to add support for parametric polymorphism to the language (basically, protocols and/or multimethods: functions that change their behaviour depending on the types of the values they receive, with the possibility of adding new input_types->method_implementations at a later time)

If it is purely stack based that might be difficult I’d think… Are you going to have some kind of heap that can be used or are going to require a protocol definition structure be passed around the code generation calls?

There is one part of the environment that is not the stack, namely the table of defined functions. It is possible to define a function using def or redef. As shown, it is possible to override an existing function with a new definition. This can be used to keep track of e.g. what data types implement a certain protocol, what protocols a certain data type implements, and how to delegate to the protocol implementation currently required.