XXIIVV

Rejoice!

Rejoice is a concatenative programming language in which logic and arithmetic emerge entirely from the application of fractions. Think Fractran, without the rule-searching, or Forth without a stack.

Basics

A program is a sequence of instructions, separated by spaces, written in the postfix notation. Each instruction is a fraction that corresponds to an exchange of resources on a list of unsorted symbols. The empty bag [] is identity; x/[] always applies, and []/x only applies when x is found in the bag.

3 4 + 2 - . ( In Forth )
n^3 n^4 []/n^2 .#n( In Rejoice )

Evaluation consists of applying each fraction in a sequence, when the contents of the denominator are found in the bag: these are removed from the bag and the contents of the numerator are added to the bag; otherwise the fraction is skipped having had no effect.

[cat bat rat^2]      ( I have 1 cat, 1 bat and 2 rats )
    [cat bat^3]/rat  ( If I have 1 rat, I replace it with 1 cat & 3 bats )
    []/[rat cat^2]   ( If I have 1 rat and 2 cats, I discard them )
    yak/bat^cat      ( If I have at least as many bats as cats..
                          .. I replace the amount of cats in bats with 1 yak )
[bat^4 yak]          ( I am left with 4 bats and 1 yak )

Execution is a single sequential pass over the program data, each instruction is attempted exactly once in order and the program halts when the program counter reaches the end of the program.

Logic

Logic is implemented by sequencing fractions for each possible state of the gate. The fractions consume a dedicated symbol, which we'll call a sentinel, to ensure only one clause fires. For example, here is the implementation of a NOT logic gate:

false not
	true/[false not]
	false/[true not]
[false not] true/[false not] false/[true not] 
[true] false/[true not] 
[true]

The different cases are sequenced one after the other, here is a OR gate. Note the need for a sentinel symbol, or, that persists through each case, without which, the false case could not be reached.

x y or
	true/[x y or]
	true/[x or]
	true/[y or]
	false/or

Lastly, here is an AND gate which also needs a sentinel symbol to capture the state when both inputs aren't present in the bag.

x y and
	true/[x y and]
	false/[x and]
	false/[y and]
	false/and

Variable Exponents

Numbers are stored as counts, for example, x^5 indicates five instances of x. Variable exponents are symbols used as the exponent value in a fraction. For a fraction containing x^y, the value of y is read as the number of instances of the symbol y in the bag resolved when the fraction is evaluated. When a symbol used as an exponent is absent from the bag, its value is zero. For example, to find the sum of two values, the program creates as many instances of x as there are of y, and then drain the left-over instances of y:

x^2 y^5 ( Copy y instances onto x, then drain y: )
	x^y
	[]/y^y
[x^2 y^5] x^y []/y^y 
[x^7 y^5] []/y^y 
[x^7] 

To find the difference between two values, the program removes as many instances of x as there are of y:

x^5 y^2 ( Remove as many x as there are y, then drain y: )
	[]/x^y
	[]/y^y
[x^5 y^2] []/x^y []/y^y 
[x^3 y^2] []/y^y 
[x^3] 

All variable exponents within a single bracket are resolved simultaneously against an atomic read of the bag. No symbol inside a bracket can observe the effects of any other symbol in the same bracket. For example, the following finds whether x is equal to y:

x^6 y^6 ( Fires only when x and y are equal counts, consuming both: )
	eq/[x^y y^x]
[x^6 y^6] eq/[x^y y^x] 
[eq] 

Labels

A @label is a special syntax that represents position in the program. After each instruction, if a symbol matching the name of an existing label is present in the bag, the symbol is consumed, and execution jumps immediately to that label position. The capitalization of labels is merely a convention. For example, here is a program to find the product:

x^2 y^3

@Mul ( x y -- res )
	[Mul res^x]/y
[x^2 y^3] [Mul res^x]/y 
[x^2 y^2 res^2] [Mul res^x]/y 
[x^2 y res^4] [Mul res^x]/y 
[x^2 res^6] [Mul res^x]/y 
[x^2 res^6] 

When two labels enter the bag at the same time, one is picked at random. For example, a program that returns the result of a coin toss:

( flip a coin ) [Head Tail] 

@Head head End
@Tail tail 
@End

Anonymous Functions

A common operation is to loop over a fraction until exhaustion. Anonymous functions are syntactic sugar so that fractions that are repeatedly applied until exhaustion don't need to be named. For example, here is a program to find the quotient:

x^24 y^6 'res/x^y
[x^24 y^6] 'res/x^y 
[x^18 y^6 res] 'res/x^y 
[x^12 y^6 res^2] 'res/x^y 
[x^6 y^6 res^3] 'res/x^y 
[y^6 res^4] 'res/x^y 
[y^6 res^4] 

The Fibonacci numbers can be found with a single anonymous function, by swapping x and y while a counter n is present:

n^6 y ( n y -- n.fib y )
	'[y^x x^y]/[x^x n]
[n^6 y] '[y^x x^y]/[x^x n] 
[n^5 y x] '[y^x x^y]/[x^x n] 
[n^4 y^2 x] '[y^x x^y]/[x^x n] 
[n^3 y^3 x^2] '[y^x x^y]/[x^x n] 
[n^2 y^5 x^3] '[y^x x^y]/[x^x n] 
[n y^8 x^5] '[y^x x^y]/[x^x n] 
[y^13 x^8] '[y^x x^y]/[x^x n] 
[y^13 x^8] 

I/O

Symbols that start with . will be emitted as text and immediately removed from the bag. Symbols that start with .# will emit the number of instances of that symbol in the bag. For example: A token like .hello^3 will emit hellohellohello.

pigs^3
	( print the word "pigs:" ) .pigs:
	( print the number of pigs in decimal ) .#pigs 
pigs:3

The supported escape sequences are: \t(tab), \s(space) and \n(linebreak). Input is done by adding special symbols in the bag before evaluation.

FizzBuzz

And lastly, this would not be complete without an example of FizzBuzz:

times^100 f b

@Loop ( times -- )
	[num f b .FizzBuzz\n Loop]/[times f^3 b^5]
	[num f b .Fizz\n Loop]/[times f^3]
	[num f b .Buzz\n Loop]/[times b^5]
	[num f b .#num .\n Loop]/times
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz ..
[] es nihil, identitate. []/n es predecessore. n es successore.

incoming: left multisets tropical arithmetic paper computing wdr computer concatenative rejoice devlog fractran bagel meta 2026