Translating an existing piece of code from one language to another is often a good way to learn a language. A great long time ago, Arkady Rost posted to the mailing list a question about translating into Factor.
The original code that was being translated looked a bit like this:
for i in rangeA {
for j in rangeB {
foo(param, i, j);
}
bar();
}
The first solution he found seemed a little bit involved, and he wondered if it could be improved:
rangeA rangeB param [ foo ] curry
[ swapd [ call ] 2curry each bar ] 2curry each
Using these values, for example:
{ "a" "b" "c" }{ "1" "2" "3" }" "append append write"" printWe can get this output:
IN: scratchpad { "a" "b" "c" } { "1" "2" "3" } " "
[ append append write ] curry
[ swapd [ call ] 2curry each "" print ] 2curry each
1a 2a 3a
1b 2b 3b
1c 2c 3c
I suggested something like this:
IN: scratchpad { "1" "2" "3" } [
{ "a" "b" "c" } [ append ] with map
" " join print
] each
1a 2a 3a
1b 2b 3b
1c 2c 3c
But then Arkady pointed out that “the original task is more complicated that’s why I’ve used foo and bar in definition of the problem”.
I
suggested
an approach that takes two sequences, applies foo to create an
intermediate sequence and then applies bar to each element:
: my-func ( a b foo: ( x y -- z ) bar: ( z -- ) -- )
[ [ with map ] 2curry ] dip compose each ; inline
The readability of this can be improved quite a bit by using “fry quotations”:
: my-func ( a b foo: ( x y -- z ) bar: ( z -- ) -- )
'[ _ with map @ ] curry each ; inline
Either way, using it is pretty easy:
IN: scratchpad { "1" "2" "3" } { "a" "b" "c" }
[ append ] [ " " join print ] my-func
1a 2a 3a
1b 2b 3b
1c 2c 3c
Of course, we could also use local variables for a direct translation:
rangeA [| i |
rangeB [| j |
param i j foo
] each
bar
] each
Or some inline fancy currying to get:
param rangeA [ rangeB [ foo ] 2with each bar ] with each
Some other ideas included a
suggestion
to use the sequences.product vocabulary.
And then there might be the potential need for row polymorphism, meaning that quotations are allowed to use a generalized amount of values present on the stack when called.
It’s fun to be reminded that there are many ways to solve problems, that expressibility of your programming language is important, and that ultimately being able to perform desired computations is the focus here.
Something I’ve always respected about Python is their infamous batteries included philosophy:
The Python source distribution has long maintained the philosophy of “batteries included” – having a rich and versatile standard library which is immediately available, without making the user download separate packages. This gives the Python language a head start in many projects.
We have a similar approach in Factor, which leads us to our extensive vocabulary index. Sometimes we see new things that we could add. One recent example is Python’s shlex module for splitting a command into arguments in a shell-like manner, keeping quoted strings together.
I’m happy to say that we now have a shlex vocabulary in the main repository.
We can use it to read a command with a quoted argument:
USING: io prettyprint shlex ;
IN: scratchpad "echo -n 'hello world'" parse-shlex .
{ "echo" "-n" "hello world" }
This is also useful for options containing spaces or an empty value:
IN: scratchpad "--title='My Project' --label ''" parse-shlex .
{ "--title=My Project" "--label" "" }
For command-like lines in a configuration file, we might want to allow
comments. The two flags to shlex-split enable comments and POSIX mode
(although perhaps instead of word arguments, maybe these should be
dynamic variables…):
IN: scratchpad "copy 'annual report.txt' archive # keep a backup" t t shlex-split .
{ "copy" "annual report.txt" "archive" }
IN: scratchpad "echo '#hello' # a comment" t t shlex-split .
{ "echo" "#hello" }
Going in the other direction, we can quote a filename as one argument for a POSIX shell:
IN: scratchpad "annual report.txt" shlex-quote print
'annual report.txt'
Or turn a whole sequence of arguments into a command string, ready to copy into a terminal:
IN: scratchpad { "cp" "annual report.txt" "backup copy.txt" } shlex-join print
cp 'annual report.txt' 'backup copy.txt'
Joining and splitting preserves the original arguments, including empty strings:
IN: scratchpad { "echo" "" "hello world" } shlex-join .
"echo '' 'hello world'"
IN: scratchpad { "echo" "" "hello world" } shlex-join parse-shlex .
{ "echo" "" "hello world" }
These words work with strings; they do not execute commands or expand variables and wildcards. The quoting is intended for POSIX-style shells.
This is now available in the development version of Factor!
Sometimes a few characters are enough to show the shape of some data. Edward Tufte wrote about this concept in sparkline theory and practice, sharing how very small graphs can provide incredible insight.
I wrote a small sparkline vocabulary for Factor that can turn numbers into little Unicode sparklines:
IN: scratchpad { 0 30 55 80 33 150 } sparkline .
"▁▂▃▄▂█"
Each number becomes one of eight block characters, from ▁ to █.
The smallest value uses the lowest block and the largest uses the highest.
The result is an ordinary string that we can print in a terminal, include
in a table, or put next to a number in a report.
There is also a method that supports comma-separated numbers:
IN: scratchpad "0,30,55,80,33,150" sparkline .
"▁▂▃▄▂█"
Automatic scaling makes a chart fill the available height, but that means two very different sets of numbers can have the same shape:
IN: scratchpad { 10 20 30 } sparkline .
"▁▄█"
IN: scratchpad { 100 200 300 } sparkline .
"▁▄█"
To compare them, we can use sparkline-range with the same minimum and
maximum:
IN: scratchpad { 10 20 30 } 0 300 sparkline-range .
"▁▁▁"
IN: scratchpad { 100 200 300 } 0 300 sparkline-range .
"▃▅█"
Or use the sparklines word to automatically normalize several inputs
that use a shared range:
IN: scratchpad { { 10 20 30 } { 100 200 300 } } sparklines .
{ "▁▁▁" "▃▅█" }
Most of the implementation fits in one word:
SYMBOL: ticks
"▁▂▃▄▅▆▇█" ticks set-global
:: sparkline-range ( seq min max -- str )
max min - ticks get length 1 - / [ 1 ] when-zero :> unit
seq [ min max clamp min - unit /i ticks get nth ] "" map-as ;
We divide the range into intervals, clamp each value, and use integer division to select its character.
Tiny charts, tiny implementation!
Factor includes support for runtime code reloading, something that I have demoed previously using the included tetris game vocabulary. It is one of my favorite features, and part of the magic that makes for fun development using the Factor programming language.
I previously wrote about using Raylib in Factor, and we now support Raylib 6.0. Over the years, some demos have been shared by the Factor community, and I recalled one in particular that was shared on the Factor Discord server that I thought was particularly neat and highlights hot code reloading.
This is a live coding demo by and Null:
Really neat!
To give it a try yourself, build Factor from the latest development
branch with Raylib 6.0 installed,
then open the UI Listener and USE: raylib.live-coding, running your code in
a with-live-coding block.
I’ve had fun watching the Bun project over the years. It has been neat in particular to see the efforts they have made to improve the performance of the JavaScript ecosystem. One of the main voices in that effort has been Jarred Sumner, who shared a small JavaScript program a bit more than a year ago:
Jarred Sumner on spawning cat
This benchmark repeatedly starts cat in batches of 100 processes. Each child
reads the JavaScript source file, with standard input, output, and error redirected
to /dev/null. There is no shell involved, and each batch waits for all of
its children to exit before starting the next one.
I was curious how Factor would perform and decided to benchmark this on an Apple Silicon Mac, taking advantage of our recently stabilized Native ARM64 builds.
Unfortunately, the answer was not good!
It took 5.445 seconds to run a Factor equivalent benchmark, compared to the 1.936 seconds it took to run the 10,000 process JavaScript benchmark in Bun 1.4.2.
Yikes!
I was, however, able to make a few improvements to Factor’s process launcher:
/dev/null descriptor.PATH candidates before calling posix_spawn, with a fallback to posix_spawnp.SIGCHLD notifications to wake the waiting thread when children exit.I’m excited to say that we now run the “spawning cat” benchmark at about the same speed, on an Apple Silicon Mac, launching and waiting for 10,000 processes takes about two seconds:
| Runtime | Time |
|---|---|
| Factor 0.102 | 1.951 seconds |
| Bun 1.4.2 | 1.936 seconds |
This is available in the development version of Factor.
The Factor UI framework is a tree of gadgets — labels, buttons, tracks, tables, panes, and others. While we have pretty good documentation in some places, sometimes it can be a little sparse in others. For learning, nothing beats seeing how a gadget actually looks and what the code that generates it looks like.
Recently, I wrote a ui-demo vocabulary. At the moment, it contains one section per gadget type. You can click around and see some examples.
Each section right now is one small word. For example, here is the whole packs section shown above:
: <three-boxes> ( pack -- pack )
{ 3 3 } >>gap
COLOR: DodgerBlue { 60 25 } <color-box> add-gadget
COLOR: MediumSeaGreen { 90 25 } <color-box> add-gadget
COLOR: chocolate1 { 45 25 } <color-box> add-gadget ;
: <packs-section> ( -- gadget )
<section>
"A pack lays its children out along one axis, each at its preferred size." <prose> add-gadget
"Shelf, horizontal:" <heading> add-gadget
<shelf> <three-boxes> add-gadget
"Pile, vertical:" <heading> add-gadget
<pile> <three-boxes> add-gadget
"Filled pile, children stretched across:" <heading> add-gadget
<filled-pile> <three-boxes> add-gadget
<page> ;
You can check it out from within a Factor listener:
IN: scratchpad "ui-demo" run
Or try ./factor -run=ui-demo from the command line.
I am perhaps susceptible to nerd-sniping and it often leads me down interesting rabbit holes. Today was one of those days. I bumped into a fun article about some obfuscated bash code on a T-shirt for sale:
The obfuscated code in question is actually an easter egg, it’s being supplied via Uniqlo stores on an excellent t-shirt designed by Akamai in support of their Peace for All campaign.
While reading about the author’s use of
OCR to
convert the printed text into a string that they could compute base64 --decode on to see the resulting program, I had major nostalgia for
carefully typing programs from computer
magazines so they could be
run locally – the only option for us kids at the time.
Raylib can be a great tool for doing colorful animations and is pretty well supported by Factor. I thought it would be fun to show how to write a similar program using it!
First, we define some constants for our message (infinitely looping using a circular sequence), font sizes, text colors, and then a computed number of visible text rows:
CONSTANT: message $[ "♥PEACE♥FOR♥ALL" <circular> ]
CONSTANT: width 800
CONSTANT: height 600
CONSTANT: font-size 24
CONSTANT: freq 0.2
! colors move from cyan to orange
CONSTANT: color-start S{ Color f 0 255 255 255 }
CONSTANT: color-end S{ Color f 255 135 0 255 }
: rows ( -- n ) height font-size /i ;
The default font doesn’t include a heart glyph, so we need to be able to manually draw one:
:: draw-heart ( x y color -- )
font-size :> s
x s 0.30 * + >integer y s 0.32 * + >integer s 0.22 * color draw-circle
x s 0.70 * + >integer y s 0.32 * + >integer s 0.22 * color draw-circle
x s 0.50 * + y s 0.95 * + <Vector2>
x s 0.92 * + y s 0.42 * + <Vector2>
x s 0.08 * + y s 0.42 * + <Vector2>
color draw-triangle ;
Otherwise, we draw our character as a function of a “tick”, moving
horizontally in x according to a sine
wave, and changing colors within
our color range:
: wave-x ( tick -- x )
freq * sin width 4 /i * width 2 /i +
round >integer 0 width font-size - clamp ;
: wave-color ( tick -- color )
[ color-start color-end ] dip rows mod rows /f color-lerp ;
:: draw-glyph ( tick row -- )
tick wave-x :> x
row font-size * :> y
tick message nth :> ch
tick wave-color :> color
ch CHAR: ♥ = [
x y glyph color draw-heart
] [
ch 1string x y font-size color draw-text
] if ;
We can now define a rendering function that we use each tick, that draws a glyph on each row:
: render ( tick -- )
begin-drawing
BLACK clear-background
rows <iota> [ [ + ] keep draw-glyph ] with each
end-drawing ;
And then a simple loop where we open a window, and increment the tick and render each frame:
: open-peace-window ( -- )
width height "♥ PEACE FOR ALL ♥" init-window
30 set-target-fps ;
: peace-for-all ( -- )
open-peace-window 0
[ window-should-close ] [ [ 1 + ] [ render ] bi ] until
drop close-window ;
It looks pretty good!
The code for this is on my GitHub.
We have a contributor that has been working hard on support for native ARM64 compilation in Factor. Simultaneously, we are working on getting that into the C++ VM as well as the new Zig VM that might eventually replace it in a future version.
I wanted to give a preview now that the native ARM64 version seems to work pretty well on macOS. Now that we are native, we expect significantly improved performance. You can get a sense of that by looking at our “core bootstrap time”:
Using the Intel version with Rosetta2:
Core bootstrap completed in 9 minutes and 6 seconds.
Using the Apple silicon native version:
Core bootstrap completed in 2 minutes and 20 seconds.
It hasn’t yet passed the entire test suite, and is not available yet as a nightly build. But, if you feel adventurous, you can grab the latest development version and bootstrap your own Super Fast Native(tm) version:
$ zig version
0.16.0
$ zig build --release=fast
$ ./factor -i=boot.unix-arm.64.image
$ ./factor
Factor 0.102 arm.64 (2311, heads/master-e4bd6e56b3, Jun 1 2026 14:22:54)
[Zig 0.16.0 ReleaseFast] on macos
Give it a try!
planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.