[ planet-factor ]

John Benediktsson: Translation

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();
}

Version 0

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:

  • rangeA: { "a" "b" "c" }
  • rangeB: { "1" "2" "3" }
  • param: " "
  • foo: append append write
  • bar: "" print

We 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

Version 1

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”.

Version 2

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

More Ideas

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.

Fri, 2 Oct 2026 15:00:00

John Benediktsson: Shlex

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!

Tue, 29 Sep 2026 15:00:00

John Benediktsson: Sparklines

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!

Mon, 28 Sep 2026 15:00:00

John Benediktsson: Raylib Live Coding

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.

Sun, 27 Sep 2026 14:00:00

John Benediktsson: Spawning Cat

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:

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:

  • Inherit the existing environment directly when there are no overrides.
  • Have the discarded standard streams share one temporary /dev/null descriptor.
  • Check PATH candidates before calling posix_spawn, with a fallback to posix_spawnp.
  • Use kqueue’s 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.

Sat, 26 Sep 2026 15:00:00

John Benediktsson: UI Demo

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.

Sat, 29 Aug 2026 15:00:00

John Benediktsson: Peace For All

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.

Wed, 8 Jul 2026 15:00:00

John Benediktsson: Native ARM64

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!

Mon, 1 Jun 2026 15:00:00

Blogroll


planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.

Syndicate