Joyful Programming: Insertion Sort

| tech

Joy is a purely functional, stack-based concatenative programming language invented by Manfred von Thun. It enables a programming paradigm called tacit programming or point-free style (aka pointless style among haters). Von Thun (2001) gives a very concise Joy-implementation of quicksort in just one line:

[small] [] [uncons [>] split] [swapd cons concat] binrec

Of course this is a hand-picked example that suits the language well. So I thought, what about implementing an algorithm that is more at home in an imperative paradigm, like insertion sort? To add to the challenge, we'll use the stack-based variant of the algorithm described by Ammar (1989).

We get a list of elements as input and use two stacks, which we'll call asc and desc, as auxiliary data structures. After execution, the function should leave only the sorted input list on the stack. The Python-like pseudocode for the main loop of the algorithm looks like this:

for element in elements:
    if element > top of asc:
        while element > top of desc:
            move top of desc to asc
        push element to desc
        move top of asc to desc
        while element < top of asc
        push element to asc

The following loop invariants must hold: asc contains elements in ascending order, desc contains elements in descending order, and the top of asc is less or equal the top of desc. If we pop all elements of desc and push them to asc, we should end up with a sorted list.

We start with some auxiliary function definitions.

null_asc    == [pop null] nullary;
null_desc   == [pop pop null] nullary;
gt_asc      == null_asc [true] [[swap first >] nullary] branch;
lt_desc     == null_desc [true] [[rolldown first <] nullary] branch;
push_asc    == swons;
push_desc   == rolldown cons swap;
asc_to_desc == [uncons [swons] dip] dip;
desc_to_asc == rotate uncons [swons] dip rotate.

There are functions for checking whether the stacks are empty, for comparing the top of each stack with the current element, for pushing the current element to one of both stacks and to move elements from one stack to another. To operate on the right data, we need to carefully manipulate the stack when entering the function and before returning, we need to make sure to leave the stack in a valid state, that is, the descending list should be pushed first, then the ascending list and finally the current element. The four boolean checks should not consume any elements from the stack, which is ensured by the nullary combinator. In the greater than and less than checks, an or would be clearer, however, Joy's or is not short-circuit evaluated, so I use the branch combinator. Probably my functions are not idiomatic at all and there is a way to get rid of half the code by some clever stack manipulations, but this set of definitions does the job and encapsulates the messy part.

With the auxiliary functions in place, we can easily translate the initial pseudocode. Put the two stacks before the input list and then step through the input to collect the elements in the ascending and descending stacks. Finally, combine both stacks into one, which will be the sorted list. Done.

insertion_sort == [[] []] dip
        [[lt_desc not] [desc_to_asc] while
        [[gt_asc not] [asc_to_desc] while
    ] step
    [swons] step.

Alright, maybe a few tests, just to make sure it works.

test_empty  == [] insertion_sort null;
test_single == [1] insertion_sort [1] equal;
test_ten    == [4 3 9 2 0 6 1 7 8 5] insertion_sort 
               [0 1 2 3 4 5 6 7 8 9] equal.

] [i] all .

This returns true, so everything seems fine.

Ammar, R. A. (1989). Stack-based sorting algorithms. Journal of Systems and Software, 9(3), 225-239.

Von Thun, M., & Thomas, R. (2001). Joy: Forth’s functional cousin. In Proceedings from the 17th EuroForth Conference.

This page was moved from my old website.