Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Perl has #1, it's called "local". Any "our" global variable can be automatically overridden for anything in the current scope, and is automatically returned to its original value once the scope ends. One of my favorite uses is to store some concept of the "user's role" in such a variable, but when I temporarily need superuser access for some reason, I give myself a scope, "local" the superuser role, do my thing, close the scope, and don't spend a lot of time worrying about whether the user will get accidentally upgraded to a superuser.

I'm not sure any other language has the exact Lisp behavior for #3, but all the pieces in various combinations are certainly around. Perl actually has the ability to do some kooky leaping around its closures, though it requires some syntax:

   sub x {
       my $y = sub { goto TEST; };

       $y->();

       print "will not see\n";

       TEST: print "here we are\n";
    }

    x();
Which is not exactly what was described, but can be used in some of the places where you'd use that.


Lua gets closures right. The implementation (http://www.lua.org/doc/jucs05.pdf, pgs. 8-10) is a bit novel - essentially, closures are stack-allocated, but migrated to the heap if still active when the function returns. (This is partially a trade-off to keep compile-time analysis very brief; compiled Lua source is often used as a data serialization format.)

Lua also has support for calling code with a user-defined error handler before unwinding the stack (xpcall), but AFAICT there isn't a way to use it to resume where the error occurred. I tried making a library for restartable exception handling in Lua, but the complete infrastructure isn't there (yet). Maybe a "power patch"...


Perl has #1, it's called "local". Any "our" global variable can be automatically overridden...

And not just our variables but also subroutine calls can be localised in the scope:

    sub bar { 'bar' }
    sub baz { bar() }

    sub foo {
        no warnings 'redefine';
        local *bar = sub { 'My Bar' };
        baz();
    }

    say foo();   # => 'My Bar'
    say bar();   # => 'bar'
    say baz();   # => 'bar'
Thus you have localised monkey patching.


Once you have dynamically scoped variables, you can implement dynamically scoped functions. There's a really great paper by Pascal Costanza showing how to to this, and why it's the mechanism behind aspect-oriented programming (no, it's not just monkey-patching!): http://www.scribd.com/doc/47747298/Dynamically-Scoped-Functi...

You can generalize this to dynamically scoped object slots or whatever else you want, which Pascal does in the ContextL library: http://common-lisp.net/project/closer/contextl.html

Actually, you don't even need built-in dynamic scoping, you can emulate it using unwind-protect (try-finally), which can itself be emulated: http://home.pipeline.com/~hbaker1/MetaCircular.html

Once you have all that, you can implement Common Lisp's condition system. Here's Red Daly's implementation for JavaScript (Parenscript): https://github.com/gonzojive/paren-psos/commit/6578ad223515d...

Of course, none of this is very useful without macros. For the condition system, you also have to be careful because any intermediate function that doesn't follow the protocol might swallow the exception.

The reason you want dynamic scoping is because it completely obviates the need for dependency injection or the service locator pattern. DI is IMO by far the stupidest fad in object-oriented programming, which is no small feat.

The Common Lisp condition system is great for doing things like writing network libraries. For example, an HTTP client library can throw any exception, like timeout, transfer error, encoding error, whatever, and offer a restart to let your retry the request (in just one place, independent of the exceptions). If you just want to retry but don't care why the thing failed, you do that. If you don't care about retrying, you just catch the error and report it, or whatever you want.

The other thing about the Common Lisp condition system is that it provides introspection on conditions and restarts. This means I can do things like offer correct condition support for programs running across thread (and, eventually, network) boundaries transparently in Eager Future2 (http://common-lisp.net/project/eager-future/), in a portable way. By contrast, it's going to be impossible for C++ libraries using C++0x futures to be correct wrt exceptions without leaking their abstractions all over the calling code (if you don't wrap the exception, the information is not correct because who knows what thread/environment the future executed in, and if you do wrap it, you change the entire API).


And array/hash slices/elements, including the deletion of a key.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: