Re: Factor

Factor: the language, the theory, and the practice.

std::flip

Monday, September 29, 2025

#syntax

Morwenn posted a blog about implementing a std::flip operation in C++:

This is basically walking up the tree from the child node as if it were a linked list. The reverse operation either implies walking through two children nodes, or simply flipping the order of parameters, which is where std::flip intervenes:

auto is_descendant_of = std::flip(is_ancestor_of);

// This property should always hold
assert(is_descendant_of(node1, node2) == is_ancestor_of(node2, node1));

Spoiler: the std::flip operator is not part of the C++ standard library, although an implementation is providing at the end of the blog post in around 90 lines of code.

Still, I thought it would be fun to implement in Factor.

As it turns out, we already have a flip word that modifies a sequence, essentially by returning the transpose of a matrix. One could argue that transpose might be a better name for that operation. In any event, let’s focus on implementing the std::flip operation.

How would we reverse the arguments to a word?

  • a b can become b a by calling swap.
  • a b c can become c b a by calling swap rot.
  • a b c d can become d c b a by calling swap rot roll.

We can generalize this into a macro by repeatedly calling -nrot:

MACRO: reverse-stack ( n -- quot )
    0 [a..b) [ '[ _ -nrot ] ] map [ ] concat-as ;

And then show that it works:

IN: scratchpad { } [ 0 reverse-stack ] with-datastack .
{ }

IN: scratchpad { "a" } [ 1 reverse-stack ] with-datastack .
{ "a" }

IN: scratchpad { "a" "b" } [ 2 reverse-stack ] with-datastack .
{ "b" "a" }

IN: scratchpad { "a" "b" "c" } [ 3 reverse-stack ] with-datastack .
{ "c" "b" "a" }

IN: scratchpad { "a" "b" "c" "d" } [ 4 reverse-stack ] with-datastack .
{ "d" "c" "b" "a" }

IN: scratchpad { "a" "b" "c" "d" "e" } [ 5 reverse-stack ] with-datastack .
{ "e" "d" "c" "b" "a" }

Using this, we can build some syntax that takes the next token and searches for a matching word with that name, and then calls it after reversing the inputs:

SYNTAX: flip:
    scan-word [ stack-effect in>> length ] keep
    '[ _ reverse-stack _ execute ] append! ;

As an example, we will use the 4array word that returns an array consisting of four arguments from the stack.

IN: scratchpad 10 20 30 40 4array .
{ 10 20 30 40 }

IN: scratchpad 10 20 30 40 flip: 4array .
{ 40 30 20 10 }

And only 5 lines of code in total.

Pretty cool!