A little interlude since third day of Lua. Today I was playing with Factor, a stack orientated, concatenative programming language with a postfix syntax. You heard me right, Factor’ uses Reverse Polish Notation. Interesting idea.
h(g(f(42))); and contrasts it with Factor
42 f g h
That is a lot cleaner and less noisy.
It also reminded me of the way that I might compose pipes when working in the bash shell. Like this
ls -alh | grep '[0-9][M]' | cut -d' ' -f6-12 | sort -rn | head -n3 That is a hacky way to find the 3 largest files (less than a gigabyte) in a directory, if you are interested.
Installing factor on Debian Jessie was a bit of a faff to be honest. It seems that it expects libraries to end in .so, whereas Debian ends them .so.1 or .so.2 and so on. To get the Listener running I just put a bunch of symlinks in place. Naughty, I know.
sudo ln -s /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so
sudo ln -s /usr/lib/libgtkglext-x11-1.0.so.0 /usr/lib/libgtkglext-x11-1.0.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so.0 /usr/lib/x86_64-linux-gnu/libgdk_pixbuf-2.0.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0 /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libcairo.so.2 /usr/lib/x86_64-linux-gnu/libcairo.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so.0 /usr/lib/x86_64-linux-gnu/libpangocairo-1.0.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libpango-1.0.so.0 /usr/lib/x86_64-linux-gnu/libpango-1.0.so
sudo ln -s /usr/lib/libgdkglext-x11-1.0.so.0 /usr/lib/libgdkglext-x11-1.0.so
Rather than a normal REPL, Factor comes with a deveopment environment called the Listener. Which sounds sort of Orwellian.
It is fine, but the GUI is not really the sort of thing I like to work with. I played around a bit to make it more readline like (i.e. using the up arrow to go back in my history rather than CTRL-P). That made things better for me.
First I installed rlwrap
$ sudo aptitude install rlwrap Then I called the Listener in commandline mode from rlwrap
$ rlwrap ./factor -run=listenerThat was a bit nicer.
Once I had got my environment set up, using Factor was actually really good fun. I enjoyed the crazy back to front syntax and the simplicity of composing functions to pop and fiddle with the stack. The exercises werr well explained and reinforced the content of the chapter well, I thought.
Using only * and + how would you calculate 3^2 + 4^2
Assuming that I am allowed to use integers too ;-)
3 3 * 4 4 * + .
Enter USE: math.functions in the Listener. Now, with sq and sqrt calculate the square root of 3^2 + 4^2
3 sq 4 sq + sqrt .
If you had the numbers 1 2 on the stack, what code could you use to end up with 1 1 2 on the stack
Enter USE: ascii in the Listener. Put your name on the stack and write a line of code that puts "Hello, " in front of your name and converts the whole string to uppercase. Use the append word to concatenate two strings and >upper to convert to uppercase. Did you have to do any stack shuffling to get the desired result?
Yep, I played around for a bit but I couldn’t think of a way to do it without shuffling the stack.
"Hello, " swap append >upper .
The reduce word takes a sequence, an initial value, and a quotation and returns the result of applying the quotation to the initial value and the first element of the sequence, then the result of applying the quotation to the result of the next element of the sequence, and so on. Using reduce, write a line of code that returns the sum of the numbers 1, 4, 17, 9, 11. Try it on your own first, but if you are truly stuck, look back carefully over the pages you've just read. There is a hint hiding somewhere.
Fine, this is just a fold but with the syntax all backwards.
[ 1 4 17 9 11 ] 0 [ + ] reduce .
Now calculate the sum of the numbers 1 to 100 in a similar fashion. Do not manually write the sequence of numbers. Instead, enter USE:math.ranges in the Listener, and use the [1,b] word to produce the sequence.
100 [1,b] 0 [ + ] reduce .
The map word takes a sequence and a quotation, and returns a sequence of results of applying the quotation to each value. Using map and the words you have learned so far, write a line of code that returns the squares of the numbers 1 to 10.
Great to see that Factor has map too.
10 [1,b] [ sq ] map .
Write a line of code that, given a number between 1 and 99, returns the two digits in the number. That is, given 42
, you should get 4 and 2 on the stack. Use the words /i, mod, and b to accomplish this task.
I wasn’t completely happy with the repeated 10 here. I am sure I could have done some stack trickery to avoid repeating myself.
42 [ 10 /i ] [ 10 mod ] bi
Repeat the previous exercise for any number of digits. Use a different strategy, though: first convert the number to a string, then iterate over each character, converting each character back to a string and then to a number. Enter USE: math.parser in the Listener and use number>strng, string>number, 1 string and each.
This is a nice way to show how trivial function composition becomes when writing in RPN style. I was impressed.
942 number>string [ 1string string>number ] each