HomeBlog

Aug
12
2012
15:23

New blog Atom feed

There is an Atom feed now at http://lisperator.net/atom.

Aug
11
2012
19:01

Announcing new website

The time has come to replace this blog.  I knew that was coming for long, but I didn't care to do it as it still somewhat works.  The reason, essentially, is that I no longer use Perl; I didn't touch this code in years and I just know that it would hurt my eyes to look into it now.

They say it's possible to write clean code in any language.  I don't think that's correct; what I deem more true is that you can write awful code in any language.  For this reason we should strive to use the most beautiful language possible.  The definition of “beautiful”, though, tends to depend on what we know already.  We often don't sense beauty because we have different, perhaps wrong, ideas about what “beautiful” is.  And of course, “beautiful” is a relative term.

For now I've settled on Common Lisp.  To me, Lisp is beautiful.  Common Lisp got somewhat ugly, but that happened for practical reasons.  It's by far better than any other language I've tried, though I will not say it's perfect.

My new blog — lisperator.net — is powered by Common Lisp and it's my online place from now on.  This site will continue to function for a while, but I will no longer update it and I will disable comments.

May
24
2012
21:43

Standards suck

Wait. I love standards and I do believe the world is far better with them. Just sayin' that no standard is perfect and motivating something that looks like a bug with “that's standard behavior” is pretty lame.

[read more...]

May
14
2012
01:04

SS-Lisp — run Lisp in your browser

SS-Lisp is an experiment I've worked on in my free time, rather sporadically, for several months.

It's a Lisp compiler that runs in a browser.  It compiles Lisp to an intermediary “bytecode” and it provides a virtual machine to run the compiled code with acceptable speed (I recommend Chrome or a derivative browser for “acceptable” to stand true).

It provides an “IDE” which mimics Emacs/SLIME, via my Ymacs editor.  The IDE has some interesting features, like symbol completion and cross-reference—if you used Emacs/SLIME you should feel fairly comfortable with it, though do not expect it to be that competent. ;)

I made a screen-cast here: https://vimeo.com/42070553 (that's without voice because I totally suck at making screen-casts).

While just a toy, as far as my research goes, I think it's the best one at the moment.  It beats BiwaScheme by orders of magnitude in terms of speed and development environment, and most other Lisp interpreters for the browser, including my own previous attempts, are not worth mentioning—except one, which actually inspired me to work on this: http://norstrulde.org/ilge10/.  Great job Eric!

Update: fellow from Germany suggested that I should rename the project, since the SS abbreviation brings up painful memories in some parts of the world...  I believe he's right so I'll rename it in the following days.  Any ideas for a name are welcome. :-)

Followup: done with that, the new name is “SLip”.  The old URL will redirect to the new one (slip.lisperator.net).

Feb
15
2012
22:26

JavaScript... wat?!

Following this hilarious talk, let me add my preferred JavaScript WAT #1.  Can you spot the error in the following code:

[ The semantics of the code aren't really important here, I'm just pasting it all to make it match real-world difficulty.  If I pasted only the line with the error + a few context lines, you'd spot it immediately. ]

[
    //// local vars namespace
    ["LVAR", "i j", {
        run: function(m) {
            //max_stat("lvar_frame", this.i);
            //max_stat("lvar_index", this.j);
            m.push(frame(m.env, this.i)[this.j]);
        }
    }],
    ["LSET", "i j", {
        run: function(m) {
            frame(m.env, this.i)[this.j] = m.top();
        }
    }],
    //// global/dynamic vars namespace
    ["GVAR", "name", {
        run: function(m) {
            m.push(m.gvar(this.name));
        }
    }],
    ["GSET", "name", {
        run: function(m) {
            m.gset(this.name, m.top());
        }
    }],
    ["BIND", "name i", {
        run: function(m) {
            m.bind(this.name, this.i);
        }
    }],
    //// global functions namespace
    ["FGVAR", "name", {
        run: function(m) {
            var f = this.name.func();
            if (!f) console.error("Undefined function", this.name);
            m.push(f);
        }
    }],
    ["FGSET", "name", {
        run: function(m) {
            this.name.setv("function", m.top());
        }
    }],
    ////
    ["POP", 0, {
        run: function(m) {
            m.pop();
        }
    }],
    ["CONST", "val", {
        run: function(m) {
            m.push(this.val);
        }
    }],
    ["JUMP", "addr", {
        run: function(m) {
            m.pc = this.addr;
        }
    }],
    ["TJUMP", "addr", {
        run: function(m) {
            if (m.pop() !== null) m.pc = this.addr;
        }
    }],
    ["FJUMP", "addr", {
        run: function(m) {
            if (m.pop() === null) m.pc = this.addr;
        }
    }],
    ["BLOCK", 0, {
        run: function(m) {
            // this is moderately tricky: we can't do
            //   m.env = new LispCons([ new LispBlockRet(m) ], m.env);
            // I'll let you figure out why.
            var frame = [];
            m.env = new LispCons(frame, m.env);
            frame[0] = new LispBlockRet(m);
        }
    }],
    ["LJUMP", "addr", {
        run: function(m) {
            m.pop().run(m, this.addr);
        }
    }],
    ["LRET", "addr", {
        run: function(m) {
            var bret = m.pop(), val = m.pop();
            bret.run(m, this.addr);
            m.push(val);
        }
    }]
    ["NOT", 0, {
        run: function(m) {
            m.push(m.pop() === null ? true : null);
        }
    }],
    ["SETCC", 0, {
        run: function(m) {
            m.uncont(m.top());
        }
    }],
    ["SAVE", "addr", {
        run: function(m) {
            m.push(m.mkret(this.addr));
        }
    }],
    ["RET", 0, {
        run: function(m) {
            var noval = m.f.noval;
            var val = m.pop();
            m.unret(m.pop());
            if (!noval) m.push(val);
        }
    }],
    ["CALL", "count", {
        run: function(m){
            var closure = m.pop();
            if (m.trace) m.trace.push([ closure, m.stack.slice(-this.count) ]);
            m.n_args = this.count;
            m.code = closure.code;
            m.env = closure.env;
            m.pc = 0;
            m.f = closure;
        }
    }],
    ["LET", "count", {
        run: function(m){
            var count = this.count;
            var frame = new Array(count);
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["ARGS", "count", {
        run: function(m){
            var count = this.count;
            if (count != m.n_args) {
                console.error(m.f);
                throw new Error("Wrong number of arguments - expecting " + count + ", got " + m.n_args);
            }
            var frame = new Array(count);
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["ARG_", "count", {
        run: function(m) {
            var count = this.count;
            var passed = m.n_args;
            if (passed < count) throw new Error("Insufficient number of arguments");
            var p = null;
            while (passed-- > count) p = new LispCons(m.pop(), p);
            var frame = new Array(count + 1);
            frame[count] = p;
            while (--count >= 0) frame[count] = m.pop();
            m.env = new LispCons(frame, m.env);
        }
    }],
    ["FRAME", 0, {
        run: function(m) {
            m.env = new LispCons([], m.env);
        }
    }],
    ["VAR", 0, {
        run: function(m) {
            m.env.car.push(m.pop());
        }
    }],
    ["VARS", "count", {
        run: function(m) {
            var count = this.count, a = m.env.car, n = a.length;
            while (--count >= 0) a[n + count] = m.pop();
        }
    }],
    ["UNFR", "lex spec", {
        run: function(m) {
            if (this.lex) m.env = rewind(m.env, this.lex);
            if (this.spec) m.denv = rewind(m.denv, this.spec);
        }
    }],
    ["FN", "code name", {
        run: function(m) {
            m.push(new LispClosure(this.code, this.name, m.env));
        },
        _disp: function() {
            return "FN(" + D.serialize(this.code) + (this.name ? "," + LispMachine.serialize_const(this.name) : "") + ")";
        }
    }],
    ["PRIM", "name nargs", {
        run: function(m) {
            var n = this.nargs;
            if (n == -1) n = m.n_args;
            var ret = this.name.primitive()(m, n);
            if (ret !== false) m.push(ret);
        }
    }],
    ["NIL", 0, { run: function(m) { m.push(null) } }],
    ["T", 0, { run: function(m) { m.push(true) } }],
    ["CONS", 0, {
        run: function(m) {
            var b = m.pop(), a = m.pop();
            m.push(new LispCons(a, b));
        }
    }],
    ["LIST", "count", {
        run: function(m) {
            var p = null, n = this.count;
            if (n == -1) n = m.n_args;
            while (n-- > 0) p = new LispCons(m.pop(), p);
            m.push(p);
        }
    }],
    ["LIST_", "count", {
        run: function(m) {
            var p = m.pop(), n = this.count;
            if (n == -1) n = m.n_args;
            while (--n > 0) p = new LispCons(m.pop(), p);
            m.push(p);
        }
    }]

].map(function(_){ defop(_[0], _[1], _[2]) });

I'm a touch typist, I get 80 wpm with close to 100% accuracy, I'm extremely careful about how I write—even though English is not my native language—well generally I'd say I pay a lot of attention to this stuff.  But still, shit happens!  The code above is valid, syntactically, although the error is blatant and I'd prefer if the JavaScript compiler would yell at me at parse time about it.  If you don't notice the problem immediately, it can take hours to debug (the error messages you get usually have nothing to do with that missing comma; if you're unlucky it can take days or weeks for the problem to manifest...  WAT.).

Even js2-mode doesn't notice it, because by JavaScript specification, that syntax is okay.

WHAT were they thinking?

Luckily I'm a grown up now and I triple-fucking-check every comma in an array of arrays.

Sep
26
2011
19:15

Does Github have a “ban” feature?

Few days ago a guy asked me on Github (as a private message) if UglifyJS can “obfuscate” code.  I didn't reply, so today he felt the need to “ask me again”:

github.png

?!?  WTF?

I had a strong urge to reply and throw some “polite” words at him, but I refrained.  What good would it do to to me or to him if I enter the game?  He's an idiot, and I don't see the fun about telling idiots that they're idiots; not anymore.

What I would like to see is a “ban” button.  I want to click that button and never again hear about this guy, even if he mentions my name; and he should be banned from commenting to any of my projects (and forks!) or from sending me any private messages.  That would be Good.  Where's that button, Github?

Jul
22
2011
01:50

Dynamic scope & lexical scope — how?

Some time back I started working on a Lisp interpreter in JavaScript.  That's because I've been going through SICP, and section 4.1 is extraordinarily inspiring—if you read that, you want to write a Scheme interpreter.  Except that, being already a Common Lisp programmer, I wanted to write a Common Lisp interpreter.  Or at least a tiny part of it.

The version that's published on Github right now has both static (lexical) and dynamic scope, but it's somewhat cheating.  To implement dynamic scope I used JavaScript's try/finally, in order to pop dynamic bindings once a LET block that established them finished execution.  However, that version has a show-stopper bug: because JavaScript doesn't have tail call optimization (and even if it would)—it ruins the stack quite easily and you can't generally do loops via recursion.

So I started a new branch (which will be on Github pretty soon); on this new version all the interpreter is implemented in “continuation-returning-style”, if I may say so—instead of calling another function, or returning a value, each expression returns a function that knows what to do next.  The evaluation means to call a function, and the returned function, and the function returned from the returned function, until the cows come home.  (trampoline-style)

This incidentally gave me call/cc “for free”, which is nice (I already gave up my dreams of implementing a subset of Common Lisp, so I'll implement a superset instead :-p).  However dynamic variables turn out to be quite a hassle now.  I don't have the luxury of using the exceptions of the underlying language, because the stack is cleared after each expression evaluates.  Somehow I need to catch the moment when a scope exits, in order to restore the dynamic variables that it modified, but I'm not quite sure how—and the problem is exaggerated by having continuations...   Grr, I'll think of it some more this weekend.

Anyway, I'll publish a toy Lisp environment, with an Ymacs demo for the browser.  Soon.  It will be totally useless for practical purposes, of course.  It can solve WOTF in 10 seconds, with a Schemeish amb macro. (100 times slower than the plain JavaScript implementation).

Update: this is it:

Still no dynamic scope.

    Apr
    09
    2011
    21:09

    Amb() in JavaScript, take two

    (this is a follow-up on a first article I wrote about amb in JavaScript).

    I kept reflecting on this—mostly because the first version was unable to help solving “Einstein's puzzle”, and I got determined to find a fix.

    So what, in fact, should amb do?  It has to pick a value and make sure something succeeds with that value.  What is something?  In the Scheme version of the operator, it's the “current continuation”.  That is, if you use amb multiple times in your program, when one fails only that one restarts (well, and subsequent ones) — it doesn't trigger a restart for the previous amb calls which didn't (yet?) fail.

    When taken to the simplest description, it's clear that it should look like this:

    [ read more... ]

    Apr
    07
    2011
    16:21

    Amb() in JavaScript

    You might have heard of the mysterious “ambiguous operator”.  Just for fun, I wrote an implementation of it in JavaScript.  It might even be useful. ;-)

    So what is amb?  Given a list of values, amb nondeterministically returns one of them in such a way that the rest of the program succeeds.  When it is possible, of course.  If it's not possible, then the program fails.

    Say what?  So it picks one value from a list, such that the rest of the program is successful!

    Here is a hypothetical use for this operator:

    var a = amb([ 1, 3, 5 ]);
    var b = amb([ 7, 8, 9 ]);
    if (a + b <= 10)
        fail();
    console.log(a, b); // 3 and 8, or maybe 5 and 9, etc.
    

    The exact usage will actually be a bit different in JavaScript, but the above is the way you could write it in Scheme (except the syntax) because Scheme has first class continuations.  A continuation is an "abstraction of the rest of the program" — so amb is able to “test” the rest of the program multiple times, giving it each value from the list, until it succeeds.  I find this really cool.

    I've seen a nice implementation for Common Lisp at Rosetta Code, even though Lisp doesn't have first-class continuations.  With macros it can be made to look almost as a part of the language.  That inspired me to try the JavaScript version.  Following there are some examples of usage, and at the bottom of this article you can find the full amb code.

    [ read more... ]

    Nov
    23
    2010
    00:07

    What's missing from NodeJS

    Every now and then I keep thinking of this. What got me started to write it all down was this thread on the mailing list. Some guy who enjoys pain takes the trouble to write an E4X extension to Node (it really should be an extension to V8, but let alone that). Then another guy replies:

    “We aren't interested in language features, we're building a platform and not concerning ourselves with the language and vm allows us to build and iterate on this platform significantly faster than if we were building a language.”  (Mikeal Rogers)

    “We aren't interested in language features”! I'm not personally a fan of E4X and don't care much about having that in the language, but I do think that JavaScript needs to evolve. When I say needs I mean right now, it's not good enough.

    [ read more... ]

    See also