DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 71: Factor

Forth language is dead, and its ideas never saw much adoption into the mainstream languages, but Forth-style languages pop up every now and then, without ever getting much traction.

One of such languages has been Factor. Let's see how it improves upon Forth.

Hello, World!

You can brew install factor, but it won't be in your path, instead it installs to a weird place.

There's also unrelated factor on Linux machines that factorizes numbers:

$ factor 420
420: 2 2 3 5 7
Enter fullscreen mode Exit fullscreen mode

Anyway, Factor. The first annoying issue is that a fresh Factor program comes with no library of any kind. No print, no +, literally nothing.

If we try this:

#!/Applications/factor/factor

"Hello, World!" print
Enter fullscreen mode Exit fullscreen mode

We get this error:

$ ./hello.factor
./hello.factor

3: "Hello, World!" print
                        ^
No word named “print” found in current vocabulary search path
(U) Quotation: [ c-to-factor => ]
    Word: c-to-factor
(U) Quotation: [ [ (get-catchstack) push ] dip call => (get-catchstack) pop* ]
(O) Word: command-line-startup
(O) Word: run-script
(O) Word: run-file
(O) Word: parse-file
(O) Word: parse-stream
(O) Word: parse-fresh
(O) Word: (parse-lines)
(O) Word: (parse-until)
(O) Word: parse-until-step
(O) Word: no-word
(O) Word: throw-restarts
(O) Method: M\ object throw
(U) Quotation: [
        OBJ-CURRENT-THREAD special-object error-thread set-global
        current-continuation => error-continuation set-global
        [ original-error set-global ] [ rethrow ] bi
    ]
Enter fullscreen mode Exit fullscreen mode

So it took me about 1 minute to seriously start hating the language. The reasonable error message would be something like:

No word named “print” found in current vocabulary search path. It is defined in the following namespaces: io.
Enter fullscreen mode Exit fullscreen mode

But no, Factor is winning the prize for having the absolute worst error messages for the simplest scripts. And it's not like it's just a few libraries to remember, nope, Factor split core functionality among hundreds of micro-libraries, so enjoy your suffering if you try to code it.

Factor VSCode plugin is also of zero help here. Factor comes with an interactive app, which has help system, which you can sort of use to search this, but it's not really much improvement over just Googling it. It's a huge pain point until you memorize all the common imports.

Hello, World! Again

Once we figure out that print is in io, we can do this:

#!/Applications/factor/factor

USING: io ;

"Hello, World!" print
Enter fullscreen mode Exit fullscreen mode

Math

OK, let's add some numbers. For this we'll need 3 different imports!

#!/Applications/factor/factor

USING: io math math.parser ;

400 20 + number>string print
60 9 + number>string print
Enter fullscreen mode Exit fullscreen mode
$ ./math.factor
420
69
Enter fullscreen mode Exit fullscreen mode

Loop

Here's a simple loop that prints numbers 1 to 10, of course it needs another import:

#!/Applications/factor/factor

USING: io math math.parser kernel ;

1
[                ! 1
  dup            ! 1 1
  number>string  ! 1 "1"
  print          ! 1
  1              ! 1 1
  +              ! 2
  dup            ! 2 2
  10             ! 2 2 10
  <=             ! 2 t
]
loop
drop
Enter fullscreen mode Exit fullscreen mode
$ ./loop.factor
1
2
3
4
5
6
7
8
9
10
Enter fullscreen mode Exit fullscreen mode

We need to drop at the end, as if you have anything on the stack once Factor finishes, it will crash.

The ! is comment character. I added some examples what's goin to be the stack status after each command. In real code we'd use much more compact code without all those comments, and with related commands together not one per line.

Defining Functions

To define functions we need to do two things, pick up a namespace, and the declare function together with what top of the stack looks before and after. The stack state is heavily annotated as well.

#!/Applications/factor/factor

USING: io math math.parser kernel ;
IN: double

: double-number ( n -- m ) 2 * ;

200
[                ! 200
  dup            ! 200 200
  double-number  ! 200 400
  number>string  ! 200 "400"
  print          ! 200
  1              ! 200 1
  +              ! 201
  dup            ! 201 201
  210            ! 201 201 210
  <=             ! 201 t
]
loop
drop
Enter fullscreen mode Exit fullscreen mode
$ ./double.factor
400
402
404
406
408
410
412
414
416
418
420
Enter fullscreen mode Exit fullscreen mode

Fibonacci

#!/Applications/factor/factor

USING: io math math.parser kernel ;
IN: fib

: fib ( n -- m )
  dup          ! N N
  2 <=         ! N t/f
  ! called if <= 2
  [             ! N
    drop        ! empty
    1           ! 1
  ]
  ! called if > 2
  [             ! N
    dup         ! N N
    1 -         ! N N-1
    fib         ! N fib(N-1)
    swap        ! fib(N-1) N
    2 -         ! fib(N-1) N-2
    fib         ! fib(N-1) fib(N-2)
    +           ! fib(N-1) + fib(N-2)
  ]
  if
;

! does not remove top of the stack
: print-fib ( n -- n )
  "fib(" write
  dup number>string write
  ") = " write
  dup fib number>string write
  "\n" write
;

1
[
  print-fib
  1 +
  dup 20 <=
]
loop
drop
Enter fullscreen mode Exit fullscreen mode
$ ./fib.factor
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610
fib(16) = 987
fib(17) = 1597
fib(18) = 2584
fib(19) = 4181
fib(20) = 6765
Enter fullscreen mode Exit fullscreen mode

It's pretty much what you'd expect from a stack-based language. write is like print except it doesn't append newline. It's also in io library. I annotated stack state for the interesting function.

FizzBuzz

Here's highly annotated FizzBuzz:

#!/Applications/factor/factor

USING: io math math.parser kernel ;
IN: fizzbuzz

: is-fizz ( n -- t/f )
  3       ! N 3
  mod     ! N%3
  0       ! N%3 0
  =       ! t/f
;
: is-buzz ( n -- t/f )
  5       ! N 5
  mod     ! N%5
  0       ! N%5 0
  =       ! t/f
;

: fizzbuzz ( n -- str )
  dup                 ! N N
  is-fizz             ! N isFizz
  [                   ! N
    is-buzz           ! isBuzz
    [ "FizzBuzz" ]    ! "FizzBuzz"
    [ "Fizz" ]        ! "Fizz"
    if
  ]
  [
    dup               ! N N
    is-buzz           ! N isBuzz
    [ drop "Buzz" ]   ! "Buzz"
    [ number>string ] ! "N"
    if
  ]
  if
;

! does not remove top of the stack
: print-fizzbuzz ( n -- n )
  dup fizzbuzz print
;

1
[
  print-fizzbuzz
  1 +
  dup 100 <=
]
loop
drop
Enter fullscreen mode Exit fullscreen mode

Should you use Factor?

No.

The import system is ridiculously bad, and if you want to just play with the language casually, it overwhelms any good aspects of the language.

If you're really serious and willing to memorize all the imports, that could work, but there's really not that much of a reward at the end. It's just another stack based language, with nothing special about it, and no clear use case.

For people who want to pick a stack based language to play with, I'd recommend Postscript or one of the esoteric ones like Befunge to maximize the fun. None of them are good for serious use.

Unlike most other stack-based languages which trust you to get the stack right, Factor requires stack effects annotations, and will not compile if you get them wrong, in a sort of a "type system". This is arguably helpful, but the error messages you get are truly awful. Which is also typical of most type systems.

Code

All code examples for the series will be in this repository.

Code for the Factor episode is available here.

Discussion (2)

Collapse
jaen profile image
Jaen • Edited on

Uhh, Factor has an entire Smalltalk-ish IDE / REPL GUI built in, just using the console version is seriously counterproductive (and nobody uses it to program), so this article basically missed 95% of the interesting parts of Factor.

It's like reviewing Smalltalk while only using eg. the console-based GNU Smalltalk. [later edit: which is exactly what this series' Smalltalk review did... oh well]
Most programmers seem to have some sort of "disease" causing them to view programming environments entirely through the lens of terminals and text editors.

(personally, I don't like stack-based languages either, but I can appreciate the other parts)

Collapse
taw profile image
Tomasz Wegrzanowski Author

Comparing languages only, in mostly same environment (terminal, VSCode, OSX), and not any special IDEs was necessary to keep this series fair. That's also how vast majority of people program. The main exception to that might be data scientists with Jupyter, but Python (and Julia etc.) also works perfectly fine in both editor and REPL.

Having a fully functional REPL and editor support is an entirely reasonable expectation for a language.