Left to Right
Tuesday, August 19, 2025
An article about Left to Right Programming was posted a few days ago with a good discussion on Hacker News. It’s a nice read with some syntax examples in different languages looking at some code blocks that are structured left-to-right or right-to-left.
We can look at a few of the shared examples and think about how they might look like naturally in Factor, which inherits a natural data flow style due to the nature of being a concatenative language.
The Challenge
The blog post discusses Graic’s 2024 Advent of Code solution, written in Python:
len(list(filter(lambda line: all([abs(x) >= 1 and abs(x) <= 3 for x in line]) and (all([x > 0 for x in line]) or all([x < 0 for x in line])), diffs)))
And compares it to an equivalent improved form in JavaScript:
diffs.filter(line =>
line.every(x => Math.abs(x) >= 1 && Math.abs(x) <= 3) &&
(line.every(x => x > 0) || line.every(x => x < 0))
).length;
There’s nothing quite like syntax wars – the nerd version of the linguistic wars – to get people interested in a topic. It is only one dimension, but perhaps the most visible one, to evaluate a programming language on.
I usually get excited for any code that solves a problem, and I give kudos to Graic for their efforts solving the Advent of Code! It’s often only working code that we can make iterative improvements upon, and that should be appreciated.
The Response
On Hacker News, someone shared a version using Python’s list comprehensions:
len([line for line in diffs
if all(1 <= abs(x) <= 3 for x in line)
and (all(x > 0 for x in line) or all(x < 0 for x in line))])
As well as a direct translation in Rust:
diffs.iter().filter(|line| {
line.iter().all(|x| x.abs() >= 1 && x.abs() <= 3) &&
(line.iter().all(|x| x > 0) || line.iter().all(|x| x < 0))
}).count()
And a single pass version in Rust with improved performance:
diffs.iter().filter(|line| {
let mut iter = line.iter();
let range = match iter.next() {
Some(-3..=-1) => -3..=-1,
Some(1..=3) => 1..=3,
Some(_) => return false,
None => return true,
};
iter.all(|x| range.contains(x))
}).count()
Some else shared a version using numpy arrays:
sum(1 for line in diffs
if ((np.abs(line) >= 1) & (np.abs(line) <= 3)).all()
and ((line > 0).all() or (line < 0).all()))
And another comment shared a version in Kotlin:
diffs.countIf { line ->
line.all { abs(it) in 1..3 } and (
line.all { it > 0} or
line.all { it < 0}
)
}
There was also a shared version in Python perhaps a bit more idiomatic:
sum(map(lambda line: all(1 <= abs(x) <= 3 for x in line)
and (all(x > 0 for x in line) or all(x < 0 for x in line)),
diffs))
What about Factor?
As you might imagine, I was also curious about what this would look like in Factor.
Directly translating using local
variables does up to
three passes through the line
:
[| line |
line [ abs 1 3 between? ] all?
line [ 0 > ] all?
line [ 0 < ] all? or and
] count
Checking that the
sign of all the
numbers are the same does two passes through the line
:
[| line |
line [ abs 1 3 between? ] all?
line [ [ sgn ] same? ] monotonic? and
] count
Using a short-circuit combinator prevents the second check if the first one fails:
[
{
[ [ abs 1 3 between? ] all? ]
[ [ [ sgn ] same? ] monotonic? ]
} 1&&
] count
Comparing the first value to subsequent values does a single pass through
the line
:
[
[ t ] [
unclip-slice { [ abs 1 3 between? ] [ sgn ] } 1&& [
'[ { [ abs 1 3 between? ] [ sgn ] } 1&& _ = ] all?
] [ drop f ] if*
] if-empty
] count
But, we often encourage factoring code into reusable small blocks:
: valid? ( value -- sgn/f )
{ [ abs 1 3 between? ] [ sgn ] } 1&& ;
[
[ t ] [
unclip-slice valid? [ '[ valid? _ = ] all? ] [ drop f ] if*
] if-empty
] count
But, we also often encourage writing combinators to do algorithmic things:
:: all-same? ( seq quot: ( elt -- obj/f ) -- ? )
seq [ t ] [
unclip-slice quot call
[ '[ quot call _ = ] all? ] [ drop f ] if*
] if-empty ; inline
[ [ { [ abs 1 3 between? ] [ sgn ] } 1&& ] all-same? ] count
We could even do something like the Rust version above, getting the endpoints from the first value to check that the subsequent ones match:
[
[ t ] [
unclip-slice {
{ [ dup 1 3 between? ] [ drop 1 3 ] }
{ [ dup -3 -1 between? ] [ drop -3 -1 ] }
[ drop f f ]
} cond [ '[ _ _ between? ] all? ] [ nip ] if*
] if-empty
] count
And that simplifies even more if we use range syntax:
[
[ t ] [
unclip-slice
{ 1 ..= 3 -3 ..= -1 } [ in? ] with find nip
[ '[ _ in? ] all? ] [ drop f ] if*
] if-empty
] count
As usual, there is more than one way to do it, and that’s okay.
Are any of these best? How else might we write this better?