Sunday, August 26, 2007

FactorCon wrap-up

Today is my last day in Austin. On Friday, we went jet-skiing; as it turns out, I'm not so good at driving one, so after a brief attempt which climaxed in me tipping over the jet-ski (with all three people on it), I delegated driving responsibilities to Doug's friend, also named Doug (common name in Texas?).

Eduardo had to go back to work. Me and Doug did a did a bit more hacking, Ed-less -- we got non-blocking UDP working on Windows, and Doug started working on a directory entry change notification binding for Windows. This won't be ready until Factor 0.91, but I'm going to implement the Unix side; kqueue on BSD, inotify on Linux, and whatever crap Solaris has on Solaris. Having a cross-platform directory change notification API is something I've wanted for a while; many other language implementations don't bother with features like this, but I consider it essential.

I also optimized the MD5 library a little bit, and improved the locals library (you can have local word definitions, and locals are writable). Doug updated the libs/math library for the new module system. There's a lot of cool code there; quaternions, polynomials, combinatorics, numerical integration... indispensable tools for hard-core programming.

I'd like to elaborate on one point regrading writable locals. Consider the following Lisp function:
(defun counter (n)
(values
(lambda () (prog1 n (setf n (1- n))))
(lambda () (prog1 n (setf n (1+ n))))))

This returns a pair of closures which increment and decrement a shared counter. Each closure returns the current value of the counter before changing it. The initial value of the counter is the value passed to the function, and each pair of closures has a unique internal counter value.

Using the locals library, the Factor equivalent is:
:: counter | n! |
[ n dup 1- n! ]
[ n dup 1+ n! ] ;

The ! suffix in the list of local parameter names indicates the local should be writable; now, the n! local word can be used to write to it. The Factor code is almost identical to the Lisp code, except with fewer parentheses.

In both cases, you see that we differentiate between binding and assignment, and also we are able to write to a local closed over in an outer scope; the n! word does not create a new internal variable in the counter quotation, but modifies the value in the lexical environment of the counter word.

On the other hand, some languages, such as Python, claim to support lexical closures, however the essential property of closures -- that they close over the lexical scope -- is not preserved, and assigning to a local simply creates a new binding in the innermost scope! Guys, that is retarded! Look at the workarounds these people use. It is really no better than Java's anonymous inner classes, and if you want to call those "closures", you may as well say that C# is a "dynamic" language.

8 comments:

Anonymous said...

Maybe you are simplifying matters a little or something, or maybe doing it "right" is not the goal, because there might be more uses to it with a more broken implementation.

That is, even if Factor does it right, it might just suck in some other regard.

Anonymous said...

Slava, you are misinformed about Ruby. It implements closures totally correctly:

def counter(start)
curr = start # I made this explicit, I could just use start as well.
lambda {
curr += 1
}
end

c = counter 5
3.times {
puts c.call
}

$ ruby ~/mess/current/s.rb
6
7
8

(There is a slight Ruby issue regarding lexical scope of block parameters, but this being fixed in the next major release.)

Anonymous said...

Its worthwhile to keep in mind that Python was not "designed" around an independent basis set of features. It was "designed" as much as C++ was "designed" really.

Slava Pestov said...

C++ has the C legacy, and the burden of a standards committee with many independent implementations. Python is a from-scratch, single-implementation language.

Slava Pestov said...

Christian, thanks for the correction. I edited the blog post to remove the claim about Ruby.

Anonymous said...

The point I was getting at was that Python was only designed around practical considerations, like C++.

Slava Pestov said...

And what languages are not designed around practical considerations?

Other than Unlambda and Brainfuck, of course.

Implementing fundamental features such as closures correctly is very practical indeed.

Gábor said...

a python implementation would be something like:


def counter(x):
    _x = [x]
    def inc():
        cur = _x[0]
        _x[0] = _x[0] + 1
        return cur

    def dec():
        cur = _x[0]
        _x[0] = _x[0] - 1
        return cur

    return (inc,dec)


while i agree that this is not so nice and clean as in other "proper" languages, i think it's still better than anonymous inner classes in java :-)

p.s: is there an easy way to keep the formatting of the source-code in a comment?