Left to Right
Tuesday, August 19, 2025
An article about Left to Right Programming was posted a few days ago with a good discussions on Hacker News and on Lobsters. 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()
Someone 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
However, it would be better if we only check against zero if the first check passes:
[| line |
line [ abs 1 3 between? ] all? [
line [ 0 > ] all?
line [ 0 < ] all? or
] [ f ] if
] count
And, despite still being three passes, it is better if we only check negative if the positive check fails:
[| line |
line [ abs 1 3 between? ] all? [
line [ 0 > ] all? [ t ] [
line [ 0 < ] all?
] if
] [ f ] if
] count
We can do these short-circuiting checks using a short-circuit combinator:
[
{
[ [ abs 1 3 between? ] all? ]
[ { [ [ 0 > ] all? ] [ [ 0 < ] all? ] } 1|| ]
} 1&&
] count
Checking that the
sign of all the
numbers are the same only does two passes through the line
:
[| line
line empty? [ t ] [
line [ abs 1 3 between? ] all?
line unclip sgn '[ sgn _ = ] all? and
] if
] count
Comparing the first value to subsequent values does a single pass through the
line
:
[
[ t ] [
unclip { [ abs 1 3 between? ] [ sgn ] } 1&& [
'[ { [ abs 1 3 between? ] [ sgn ] } 1&& _ = ] all?
] [ drop f ] if*
] if-empty
] count
We often encourage writing combinators to do algorithmic things:
:: all-same? ( seq quot: ( elt -- obj/f ) -- ? )
seq [ t ] [
unclip quot call [ '[ quot call _ = ] all? ] [ drop f ] if*
] if-empty ; inline
Which makes for a satisfyingly simple version:
[ [ { [ 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 {
{ [ 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 { 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?