Factor - gregorymorrison/euler1 GitHub Wiki

Factor, introduced in 2003, is a stack-based language, a descendent of Forth. If you have never explored this arcane corner of Computer Science before, then strap on a seatbelt because it's quite a trip. In this language, programs are written in postfix notation, and the compiler and runtime use a stack to manage your syntax. Non-executable tokens - strings, objects, etc - get pushed on the stack, and executable ones - functions, operators, etc - get evaluated, using and storing whatever they want onto the stack. Parameters are passed by the caller pushing them onto the stack and the called routine popping them off. Stack-based operations are a necessary part of the language - there's a lot of pushing, popping, swapping, etc. And not just tokens but whole blocks of code get stored on the stack.

The language has a rich library, including OpenGL bindings, a sockets library, a web framework, etc. And the libraries are distributed as source, so it's easy to see how things work and to find good examples of idiomatic Factor.

Here is a version of Euler1 in Factor:

! Euler1 in Factor

USING: math kernel io math.ranges math.functions
math.parser sequences prettyprint quotations ;
IN: main

: mod3_5? ( n -- ? )  [ 3 divisor? ] [ 5 divisor? ] bi or ;
: mod3_5  ( n -- n )  dup mod3_5? swap 0 ? ;
: euler1  ( n -- n )  1 [a,b] [ mod3_5 ] map sum ;

: main    ( -- )      999 euler1 print ;

Let's break it down.

mod3_5? takes an integer argument and returns a boolean:

Operation Stack
1. First, a test for modulo 3 gets pushed on the stack. ( n [ 3 divisor? ] )
2. Next, a test for modulo 5 gets pushed on the stack. ( n [ 3 divisor? ] [ 5 divisor? ] )
3. Next, the bi operator pops the two functions and the integer, performs both tests on the integer passed in, and pushes the boolean results of each. ( ? ? )
4. Finally, or pops the two boolean results and pushes its return value, another boolean. ( ? )

mod3_5 takes an integer and returns an integer. Our goal here is to return n if n is divisible by 3 or 5, else 0. The ? conditional operator can perform this test, and requires the following on the stack in this order - a boolean, a true return value, and a false return value. This function starts out with the stack ( n ), and we need to convert it to ( ? n 0 ). So:

Operation Stack
1. First, duplicate the top element, so that mod3_5? has something to test: ( n n )
2. Next, perform mod3_5?, which pops a number and pushes its evaluated boolean result: ( n ? )
3. Swap the top two, moving the boolean to the bottom: ( ? n )
4. Next, push a 0 and we are ready for our test: ( ? n 0 )
5. Finally, ? pops them all, evaluates them, and pushes the return: ( n )

euler1 takes an integer and returns an integer. This is pure side-effect-free Functional programming using map, which applies a function to all elements of a sequence:

Operation Stack
1. First, add 1 to the stack: ( n 1 )
2. Next, create a sequence. [a,b] takes two integers and returns a sequence: ( { n ... 1 } )
3. Next, add the function that map will be applying: ( { n ... 1 } [ mod3_5 ] )
4. Map pops the sequence and function, applies the function to the sequence. and pushes the modified sequence back on the stack: ( { n ... 1 } )
5. Finally, sum pops the sequence, adds them up, and pushes the result: ( n )

I have to admit, it took me forever to learn this language. It took me hours just to figure out how to write a simple conditional. I had originally gone all the way through the Factor tutorial and still had no clue what was going on, and gave up. It wasn't until I spent time with Forth that Factor finally clicked for me. Expect a huge learning curve. The mind expansion is well worth it, though, so I would highly advise any interested parties to give it a few chances.

The IDE is quite foreign, but once you figure it out, it is very nice. It has dynamic help built in, and it breaks when your program is about to crash, which is hugely helpful. And Factor's compiler and runtime error messages are quite good.

At first I was trying to draw out at each step what the stack contained, trying to trace through it on paper. However, that's a mental workout that would not cut it for actual production work. But my breakthrough came when realizing that the IDE is a REPL, yay! You can load each function in the REPL individually and test it to your heart's content.

Now, to actually run this code requires a bit of handholding. I would strongly recommend you start off by following the official tutorial, which teaches you how to compile and execute your code within Factor's REPL. This process involves turning your code into a reusable library which is well worth learning. Expect this whole process to be a bit of learning curve, since Factor has its own unique way of doing things, so I won't explain it here. My first example above will execute in the REPL.

You can run Factor as a command-line script, but it involves adding a bit of boilerplate. It took me approximately four hours(!) of frustrating trial and error to figure this out - I couldn't find it documented anywhere. Just wrap your entry point with the following command-line declaration. Also, you're supposed to be able run this code with a shebang at the top, but I could not get that to work:

! Euler1 in Factor

USING: io kernel math.functions math.ranges
prettyprint quotations sequences
command-line namespaces ;
IN: euler1

: mod3_5? ( n -- ? )  [ 3 divisor? ] [ 5 divisor? ] bi or ;
: mod3_5  ( n -- n )  dup mod3_5? swap 0 ? ;
: euler1  ( n -- n )  1 [a,b] [ mod3_5 ] map sum ;

command-line get [
    999 euler1 .
] [ ] if-empty

Now, Linux already has a built-in executable factor, so make sure you reference the correct one - this confused me for a little bit. If you get the error message, "'euler1.factor' is not a valid positive integer", you're using the wrong one. Just execute factor, passing it your script as an argument:

$ ~/factor/factor euler1.factor
233168