Monday, November 14, 2011

From Lisp to Factor

Finally, the search is over.

I've been looking for the perfect programming language for years, and Factor is the one with the most potential to become that.

Before I found Factor I was into Lisp and Scheme, and I was in love with the parentheses. But that's just because I did not know Forth - if I had known it at the time I'd be working on a language that is a cross between Lisp and Forth.

Well, Factor is that language.

For my first post I will translate the good old yin yang trick from Scheme. I was not surprised to see that this works in Factor at all, since it seems that Factor actually copies "the stack and environment", much like the way schemers pretend call/cc works when explaining it to people. [1]

What yin yang does is it prints this ad infinitum:
@*@**@***@****@*****@****** ...

Here's the Scheme code:

(let* ((yin
((lambda (cc) (display #\@) cc)
(call-with-current-continuation (lambda (c) c))))
(yang
((lambda (cc) (display #\*) cc)
(call-with-current-continuation (lambda (c) c)))))
(yin yang))

And here's the Factor version with a safety trigger to prevent it from looping forever:

USE: locals ;

[let
0 :> counter!
[ ] callcc1 [| cc | "@" write counter
100 >
[ "force-stopped" throw ]
[ counter 1 + counter! ] if
cc ] call :> yin
[ ] callcc1 [| cc | "*" write cc ] call :> yang
yang yin continue-with ]


It will print something like this (without the newlines):

@*@**@***@****@*****@******@*******@********@
*********@**********@***********@************
@*************@**************@***************
@****************@*****************@*********
...

If you understand continuations you can read up on the docs for callcc1 and continue-with and immediately understand how this works exactly like the Scheme version (or how it can be made to do so if the safety trigger is removed).

[ ] callcc1 is the literal equivalent to (call/cc id) that you see above in its more verbose version.

continue-with is like Scheme's apply but only for continuations; it takes a continuation to be called, and one argument to be passed to it. Continuations and lambdas in Factor are not isomorphic, so we need a way to call continuations. arg k continue-with is equivalent to (k arg).

I'm using locals above to make it simpler to see, but the snippet would be shorter if I did away with the cc locals:

USE: locals ;

[let
0 :> counter!
[ ] callcc1 [ "@" write counter
100 >
[ "force-stopped" throw ]
[ counter 1 + counter! ] if
] call :> yin
[ ] callcc1 [ "*" write ] call :> yang
yang yin continue-with ]


[1] I believe only a few schemes implement call/cc with any copying at all. It is possible to get call/cc for free, i.e. its cost being the same as of a normal function call, and it is also possible that this way of implementing call/cc is actually easier than alternatives.

No comments:

Post a Comment