Factor contains a REPL – called the Listener – available on the command-line and graphically as part of the UI developer tools. For many users, this is their main interface to programming in the Factor programming language.
We sometimes get requests to better support styling the user interface. This has led to improvements such as support for light and dark themes, adjustable font sizes, and other customizations. There have been a few existing ways to style the UI listener including support for keyboard commands to increase or decrease font sizes. But, until recently this only affected new output or new Listener sessions.
Today, I improved this to make adjusting the font size much more dynamic, using
traditional keyboard shortcuts of Ctrl
– or Cmd
on
macOS – combined with +
or -
:
Give it a try, and please let us know other ways we can make improvements!
Recently, I’ve been inspired by conversations taking place on our Factor Discord server. This sometimes reflects areas of interest from new contributors, curiousity exploring similarities and differences between Factor and other programming languages, or even early learning moments when exploring concatenative languages in general.
Today, someone asked about how to think about “accumulation of values in an array… to find all occurences (their position) of a subseq in a seq”. The solution to this might have this word name and stack effect:
: subseq-indices ( seq subseq -- indices ) ... ;
Before answering, I wanted to make sure they wanted to find overlapping indices vs. non-overlapping indices, and they clarified that they expect it to find this result – allowing overlapping subsequences:
IN: scratchpad "abcabcabc" "abcabc" subseq-indices .
{ 0 3 }
So, now that we have a reasonable specification, how do we think about solving this problem when we are at the same time learning to solve problems in stack languages and trying to see what features of Factor’s standard library would help.
There are a lot of ways to think about this, and I often recommend one of three approaches:
So let’s look at each approach in turn:
The inner logic is going to require something like “take an index to start from and find the next matching subseq index”, which looks an awful lot like subseq-index-from – except you also want to increment the found index afterwards to make sure you are progressing through the sequence.
: next-subseq-index ( index seq subseq -- next-index/f found-index/f )
subseq-index-from [ [ 1 + ] keep ] [ f f ] if* ;
Then you could use it like so in a loop with an accumulator:
: subseq-indices ( seq subseq -- indices )
[ V{ } clone 0 ] 2dip '[
_ _ next-subseq-index dup [ [ pick push ] keep ] when
] loop drop ;
But that feels like we had to work hard to do that, directly using an
accumulator, conditionals, and some stack shuffling. Luckily we have some
higher level words that might help, for example the make
vocabulary
which has an implicit accumulator that we can use ,
or %
to push into:
: subseq-indices ( seq subseq -- indices )
[ 0 ] 2dip '[
[ _ _ next-subseq-index dup [ , ] when* ] loop
] { } make nip ;
Or even using a while* loop, which is less code:
: subseq-indices ( seq subseq -- indices )
[ 0 ] 2dip '[
[ _ _ next-subseq-index ] [ , ] while*
] { } make nip ;
But that feels like a lot too, simpler might be produce:
: subseq-indices ( seq subseq -- indices )
[ 0 ] 2dip '[ _ _ next-subseq-index dup ] [ ] produce 2nip ;
Or using follow, adjusting our start index and increment:
: subseq-indices ( seq subseq -- indices )
[ -1 ] 2dip '[ 1 + _ _ subseq-index-from ] follow rest ;
The outer logic approach would be something like “we need to loop from the start of the sequence, finding the next match, and accumulating it, until we hit some exit condition and then return a result” which you could write in a kind of non-functional stack pseudocode:
: subseq-indices ( seq subseq -- indices )
0 [ find-next-match ] [ accumulate-match ] while ;
Then you have to kind of figure out what goes into those blocks:
: find-next-match ( seq subseq n -- found-index/f )
-rot subseq-index-from ;
And also something like:
: accumulate-match ( accum found-index -- accum next-index )
[ suffix! ] keep 1 + ;
Taking those, and maybe thinking about what items should be on the stack and in what order to reduce stack shuffling, becomes something like:
: subseq-indices ( seq subseq -- indices )
[ V{ } clone 0 ] 2dip
'[ _ _ subseq-index-from ] [ [ suffix! ] keep 1 + ] while* ;
It is true that [ suffix! ] keep 1 +
is also [ suffix! ] [ 1 + ] bi
,
with varying aesthetics and ease of understanding, but sometimes when
learning a new language especially a stack language with
combinators,
it is sometimes easy to start with stack shuffling and then learn about
these forms later to see if they can improve your code.
Instead of those two stack approaches, we could instead use our local variables and write one big word in a manner similar to applicative languages, stepping back and focusing on the result we want:
:: subseq-indices ( seq subseq -- indices )
V{ } clone :> accum
0 :> i!
[ i seq subseq subseq-index-from ]
[ dup accum push 1 + i! ] while*
accum ;
When working on this stuff, it’s nice to remember you can put a B
to
set a
breakpoint in
places to examine the stack at some inner point, or perhaps write a comment
showing the incoming stack and optionally the outgoing stack that a piece of
code is expected to have so that you understand what is happening in the
next few lines:
! the next block of code finds the next index
! ( index seq subseq -- found-index )
! and pushes it into an accumulator
! ( accum found-index -- accum )
This was added to the developer
branch
in the sequences.extras
vocabulary.
We love to hear questions and it’s even better when we can provide answers or guidance for learning and solving problems. Feel free to join our conversations and explore learning Factor!
There was an interesting question on the Unix & Linux StackExchange asking how to remove subdomains or existing domains. I thought it would be fun to show a few different approaches to solving this using Factor.
Our first step should be to understand what is a subdomain:
A subdomain is a prefix added to a domain name to separate a section of your website. Site owners primarily use subdomains to manage extensive sections that require their own content hierarchy, such as online stores, blogs, job boards or support platforms.
If we’re curious about what common subdomains are, we can turn to the SecLists project – described as a “security tester’s companion” – which maintains a list of common 5,000 subdomains, 20,000 subdomains, and 110,000 subdomains that were generated in 2015 as well as a combined subdomains list that has some additional ones added.
You can download the top 5,000 common subdomains using memoization to cache the result:
MEMO: top-5000-subdomains ( -- subdomains )
"https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/DNS/subdomains-top1million-5000.txt"
cache-directory download-once-into utf8 file-lines ;
And then see what the “top 10” are:
IN: scratchpad top-5000-subdomains 10 head .
{
"www"
"mail"
"ftp"
"localhost"
"webmail"
"smtp"
"webdisk"
"pop"
"cpanel"
"whm"
}
You could remove “common subdomains” – adding a dot to make sure we only strip a full subdomain – by recursively trying to clean the hostname until it stops changing.
: remove-common-subdomains ( host -- host' )
top-5000-subdomains [ "." append ] map '[ _ [ ?head ] any? ] loop ;
And try it out:
IN: scratchpad "www.mail.ftp.localhost.factorcode.org"
remove-common-subdomains .
"factorcode.org"
That works pretty well, but it’s reliant on a scraped list of subdomains that might not be exhaustive, and could become stale over time as the tools and techniques that developers use change.
Similarly, another technique we could use would be to use our own observations about domains, and if we observe a domain being used and then subsequently see a subdomain of it, we can ignore the subdomain.
First, we write a word to remove any item that is prefixed by another, sorting to make sure we see the prefix before the item prefixed by it:
: remove-prefixed ( seq -- seq' )
sort V{ } clone [
dup '[
[ _ [ head? ] with none? ] _ push-when
] each
] keep ;
Second, we can remove the subdomains by using a kind of Schwartzian transform:
: remove-observed-subdomains ( hosts -- hosts' )
[ "." prepend reverse ] map remove-prefixed [ reverse rest ] map ;
And then see it work:
IN: scratchpad { "a.b.c" "b.c" "c.d.e" "e.f" }
remove-observed-subdomains .
V{ "b.c" "c.d.e" "e.f" }
And, finally, another technique might be to use the Domain Name System to find the rootiest domain name.
First, we use our dns vocabulary to check that a host resolves to an IP address:
: valid-domain? ( host -- ? )
{
[ dns-A-query message>a-names empty? not ]
[ dns-AAAA-query message>aaaa-names empty? not ]
} 1|| ;
And try it out:
IN: scratchpad "re.factorcode.org" valid-domain? .
t
IN: scratchpad "not-valid.factorcode.org" valid-domain? .
f
Second, we write a word to split a domain into chunks to be tested:
: split-domain ( host -- hosts )
"." split dup length 1 [-] <iota> [ tail "." join ] with map ;
And try it out:
IN: scratchpad "a.b.c.com" split-domain .
{ "a.b.c.com" "b.c.com" "c.com" }
Third, we find the rootiest domain that is valid:
: remove-subdomains ( host -- host' )
split-domain [ valid-domain? ] find-last nip ;
And try it out:
IN: scratchpad "a.b.c.d.factorcode.org" remove-subdomains .
"factorcode.org"
IN: scratchpad "sorting.cr.yp.to" remove-subdomains .
"cr.yp.to"
This is available on my GitHub.
It’s fun to explore these kinds of problems!
Andrew Shitov recently published a book called “A Language A Day”, which is a collection of brief overviews to 21 programming languages – including Factor!
This book provides a concise overview of 21 different programming languages. Each language is introduced using the same approach: solving several programming problems to showcase its features and capabilities. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.
Each chapter covers the essentials of a different programming language. To make the content more consistent and comparable, I use the same structure for each language, focusing on the following mini projects:
- Creating a ‘Hello, World!’ program.
- Implementing a Factorial function using recursion or a functional-style approach.
- Creating a polymorphic array of objects (a ‘zoo’ of cats and dogs) and calling methods on them.
- Implementing the Sleep Sort algorithm—while impractical for real-word use, it’s a playful demonstration of language’s concurrency capabilities.
Each language description follows—where applicable—this pattern:
- Installing a command-line compiler and running a program.
- Creating and using variables.
- Defining and using functions.
- Exploring object-oriented features.
- Handling exception.
- Introducing basic concurrency and parallelism.
You can find all the code examples in this book on GitHub: https://github.com/ash/a-language-a-day.
You can buy it on Amazon or LeanPub as an electronic or Kindle edition, or as a paper hardcover or paperback version. More information with the links to the shops.
Check it out!
Factor has programmable syntax, a feature that allows for concise source code, reducing repetition and allowing the programmer to express forms and intent with minimal tokens. As an example of this, today I want to discuss constants.
You can define a word with a constant value, using syntax like this:
CONSTANT: three 3
Someone on our Factor Discord server asked if it was possible to define multiple constants in one syntax expression, to avoid the line noise of defining them one-by-one.
So, instead of these four definitions:
CONSTANT: foo 1
CONSTANT: bar $[ 2 sqrt ]
CONSTANT: baz $ bar
CONSTANT: qux \ foo
We could instead make this syntax:
SYNTAX: CONSTANTS:
";" [
create-word-in
[ reset-generic ]
[ scan-object define-constant ] bi
] each-token ;
Breaking that down into steps:
SYNTAX:
indicates we’re defining new syntaxCONSTANTS:
is the name of our new syntax word";"
defines the terminator that will end our constant definitionseach-token
will process each token until it hits the terminatorFor each constant definition, it performs these steps:
create-word-in
creates a new word in the current vocabularyreset-generic
clears any generic word propertiesscan-object
reads and parses the next valuedefine-constant
makes it a constant with the parsed valueAnd now this expression works, reducing the visual noise in our source code:
CONSTANTS:
foo 1
bar $[ 2 sqrt ]
baz $ bar
qux \ foo
;
As an aside, the different syntaxes used above are:
1
is just a token parsed as a number literal$[ ... ]
evaluates the code inside at parse time$
gets the value of another constant\
gets the word object itself rather than its valueFactor’s syntax parsing words allow a great deal of flexibility in making custom DSL-style syntax forms work nicely to reduce repetition, and generate code with less effort.
I’m not sure if this is worth adding to the standard library or not, but it’s neat!
Over a decade ago, Chris Kempson created the Base16 theme framework for creating color palettes of 16 colors that can be used to provide theming of user interfaces. These have been commonly supported by many text editors, with some developers gravitating toward setting their favorite theme in every user interface that supports it.
A few years ago, this framework and the many themes that became popular in it were forked into the Tinted Theming project described in a post called Base16 Project Lives On. You can view their gallery of Base16 themes which gives a good sense of the variety and utility of these color schemes having commonly recognizable names such as dracula, mocha, solarized, and more.
I was reminded of this recently in a discussion around a recent contribution to change the scrollbar and button implementations to not use images, but to draw the scrollbars using the colors configured in the user’s theme.
Since 2021, the ui.theme.base16 vocabulary has allowed theming the Factor user interface by choosing a base16-theme-name and setting base16-mode. We have just improved our support for Base16 theme support by adding all the current styles from the Tinted Theming schemes list.
So, now you can try solarized-dark:
IN: scratchpad "solarized-dark" base16-theme-name set-global
IN: scratchpad base16-mode
Or perhaps greenscreen:
IN: scratchpad "greenscreen" base16-theme-name set-global
IN: scratchpad base16-mode
Or any of the other 270 named color schemes now available!
Enjoy!
One of the interesting aspects of a concatenative language like Factor is that blocks of logic can be easily extracted and easily reused since they apply logic to objects on the stack.
For example, if this was a word that operated on stack values:
: do-things ( a b -- c d )
[ sqrt * ] [ swap sqrt + ] 2bi ;
One change we could easily make is to extract and name the two pieces of logic:
: calc-c ( a b -- c ) sqrt * ;
: calc-d ( a b -- d ) swap sqrt + ;
: do-things ( a b -- c d )
[ calc-c ] [ calc-d ] 2bi ;
We could also convert it to operate on local variables:
:: do-things ( a b -- c d )
a b sqrt * a sqrt b + ;
And extract those same two pieces of logic:
:: calc-c ( a b -- c ) a b sqrt * ;
:: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
a b calc-c a b calc-d ;
But, notice that we have to specify that the local variable a
and b
have to be put back on the stack before we can call our extracted words that
make the computations.
Today, someone on the Factor Discord server asked about this very issue, wanting to have extractable pieces of logic that would effectively be operating on nested local variables, wherever they are used. Inspired by the goal of don’t repeat yourself and the convenience of extracting logic that operates on the data stack.
Specifically, they wanted to be able to take blocks of logic that operate on named variables, and extract them in a similar manner to the logic blocks that operate on the stack – offering this hypothetical syntax as the goal:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Let’s try and build real syntax that allows this hypothetical syntax to work.
First, we make a tuple to hold a lazy variable binding:
TUPLE: lazy token ;
C: <lazy> lazy
Then, we need a way to generate temporary syntax words in a similar manner to temporary words:
: define-temp-syntax ( quot -- word )
[ gensym dup ] dip define-syntax ;
We create temporary syntax words to convert each named references to lazy variables:
: make-lazy-vars ( names -- words )
[ dup '[ _ <lazy> suffix! ] define-temp-syntax ] H{ } map>assoc ;
Given a quotation that we have parsed in an emit description, we can build a word to replace all these lazy variables by looking them up in the current vocabulary manifest:
: replace-lazy-vars ( quot -- quot' )
[ dup lazy? [ token>> parse-word ] when ] deep-map ;
And, finally, create our emit syntax word that parses a definition, making lazy variables that are then replaced when the emit word is called in the nested scope:
SYNTAX: EMIT:
scan-new-word scan-effect in>>
[ make-lazy-vars ] with-compilation-unit
[ parse-definition ] with-words
'[ _ replace-lazy-vars append! ] define-syntax ;
Now, let’s go back to our original example:
EMIT: calc-c ( a b -- c ) a b sqrt * ;
EMIT: calc-d ( a b -- d ) a sqrt b + ;
:: do-things ( a b -- c d )
calc-c calc-d ;
Does it work?
IN: scratchpad 1 2 do-things
--- Data stack:
1.4142135623730951
3.0
Yep! That’s kind of a neat thing to build.
I have added this syntax in the locals.lazy vocabulary, if you want to try it out.
I’m not sure how useful it will be in general, but it is always fun to build something new with Factor!
planet-factor is an Atom/RSS aggregator that collects the contents of Factor-related blogs. It is inspired by Planet Lisp.