May 30, 2003

Quicko MT upgrade

Seems there were a few unpleasant bugs in versions of MT through 2.63. I expect an announcement of what and how will be made at some point, but if you're running moveable type, I'd recommend upgrading to version 2.64.

Posted by Dan at 12:37 PM | Comments (2) | TrackBack

May 29, 2003

Software Engineering. Yeah, right.

I'm sitting here watching "Extreme Engineering" on the Discovery Channel. This episode is about Boston's Big Dig, a project to move Interstate 93 from above the streets of Boston to beneath them, as well as extend Interstate 90 (under Boston Harbor) to reach Logan Airport. The number of people involved, projects being managed, and unexpected problems that they ran into, is absolutely astounding.

Despite an insane number of constraints--the tunnels needed to run under the above-ground rail lines, beneath the one of the underground red line subway stations, and over the red line tunnel that was running under Boston Harbor itself--as well as the inevitable Feature Creep you get when $10B+ is being spent (I think they're up to $15 billion dollars) and politicians are involved, they're pulling it off. Without shutting down (Though they are slowing things down in spots) the existing highway and street systems. The system is mind-boggling, and has been going for more than a decade, and they're pulling it off.

We, on the other hand, can't manage to build a PC operating system that doesn't suck dead badgers through 12" metal conduit.

Not that this is an entirely fair grump, as there's a lot of software involved in building and managing the Big Dig and the tunnel maintenance and monitoring systems. I can only presume it's not being designed and managed by hordes of over-caffienated, ADHD programmers and clueless managers.

It would be so nice if we actually looked on building big projects in our profession as an engineering discipline, rather than some sort of work of abstract expressionism, or cowboy dickwaving contest.

Posted by Dan at 11:32 AM | Comments (7) | TrackBack

May 27, 2003

What the heck is: Multimethod dispatch (MMD)

AKA Signature-based dispatch, and a number of other names.

This is a way for your program to have multiple subroutines, functions, or methods with the same name that differ only in the types of their signatures. That means you can have a subroutine foo that takes an integer, one that takes a string, and a third that takes a pointer to a structure of type bar. Same name, different parameter types. The compiler or your runtime then figures out which one to use. With statically typed languages it can be done at compile time, with dynamically typed languages it's done at runtime.

In fact, your code might look like:

sub foo(int a) {
print "An integer!";
}

sub foo(float a) {
print "A float!";
}

sub foo(string a) {
print "A string!";
}

What happens is that, when your code calls foo(), something (either the compiler or the runtime) takes a look at its parameter and decides which of your foo subs should be called. If the parameter's an int, the first foo is called. If it's a float, the second function is called, and if it's a string, the third function is called. This can be something of a disconcerting thing, if you're not used to it.

Now, before we go any further it's important to note that almost all languages that let you do this either require some sort of notation on the sub or method declaration, or have multimethod dispatch as a very fundamental construct, so either you've asked for it or you're expecting it. It's not, generally, something that springs itself on you. (If you were worried about accidentally doing this)

Why would you do this? Well...

If you've worked with a dynamically typed language, you've probably written code that looks something like:

method foo (object bar) {
if (bar.isa("integer")) {}
elsif (bar.isa("string")) {}
elsif (bar.isa("Thing")) {}
elsif (bar.isa("Widget") {}
}

where your method takes an object as a parameter, and at runtime you figure out what type of object it is. If you've done that, you've undoubtedly messed it up once or twice, and you've probably also used other people's code that does this and you've wanted to add a path for your object type, but couldn't.

If you've done operator overloading, you've probably also done MMD. Operator overloading (really a subject of another WTHI) is more or less when you can define how some basic operator such as addition or multiplication works for your class. Whenever you have this sort of overloading you're pretty much forced to do some sort of MMD if you want to Do The Right Thing, and that means checking the type of the other operand. Multiplication with an integer on the left and a matrix on the right does something very different than multiplication with a matrix on both sides. Or it does if you implement any sort of standard multiplication. Languages that do overloading without doing MMD make writing proper overloaded operators a pain.

Even in a language that has no concept of multimethod dispatch, it's still there, at least a little. Consider the following snippet of C:

c = a + b;

No biggie, right? Nothing multimethod there. Well... Try it. The C compiler will emit different code depending on the types of a and b. If they're both integers you'll get one set, if one of them is a float you'll get another, and if they're both floats you'll get a third. Multimethod dispatch, based on type. You just can't get to it in C.

One big benefit to multimethods is that they are generally (though not always) considered to live outside any namespace or object package, so you can add in your own foo method to any class, and it Just Works. Or, more to the point, you can add your "multiply(int, matrix)" function to the mix without having to thump the int class. (if you even can) One other benefit, in an OO language, is that generally the compiler or runtime will do the best it can. If you have a foo method that takes two strings, and instead call it with two objects that are subclasses of strings, the compiler (or runtime) will walk the inheritance tree of the arguments looking for the closest match, for some version of closest, and call that. If one can't be found, you get an error.

Now that you know what they are, there's the why question. Why do this?

Well, efficiency is one reason. You can have different versions of a routine that are optimized for the different types of the parameters. As in the C compiler case, you may want to emit different code depending on the operand types, to go as fast as possible.

Speed of dispatch is another--odds are the MMD code in the compiler or runtime is very well optimized for speed, and likely will outstrip anything you can do by hand. Even if your own code is faster, the next rev of the compiler or runtime may beat it, which is how these things often go.

Bug reduction is another. It's a lot less error prone to have one chunk of code, provided in the compiler or runtime, to figure out what routine to call based on the arguments than it is to have each of a thousand subs and methods hand-roll the code at their start. It's dull, boring code and very easy to get wrong.

Correctness is yet another reason. If you want your language to behave properly in the face of operator overloading (and you might not, depending on how you feel about overloading) you have to allow for very different behaviour based on the argument types. Matrix * Matrix does something very different than Matrix * Integer. (And different still from Integer * Matrix, but that's another story) This isn't even any sort of "I'm doing something wacky with operators!" it's just correctness.

Since multimethods generally live outside any class it makes doing proper operator overload extensions easier as well. While I know it "breaks encapsulation to inject a method into another class" you really do have to in many cases to get it right. Consider the matrix example we've been batting around. Integer * Matrix is an important case, but most operator overloading systems check the left-hand side to see what they should do. Since the Int class probably came standard, it undoubtedly has no idea what a matrix is, or what to do with it. Your Matrix class, on the other hand, does know what to do, so having the int*matrix version in the matrix class makes sense. It also means that when you create your own Foo class, which also knows about matrices (though matrix has no idea about Foo) you can have it provide code to handle the matrix case. While you can't handle all eventualities, with MMD you can at least handle more.

MMD. Interesting, useful, and in many cases what you're doing already, only in a less sucky way.

Posted by Dan at 06:13 PM | Comments (12) | TrackBack

May 26, 2003

OSCON hotelling set

Well, the last big planning thing for OSCON's taken care off--I made my hotel reservations last night. I'm not staying at the con hotel, as it's a bit pricey. Yeah, I know, $129 is cheap for a con hotel, but I found something at the Sheraton Four Points down the park for $69 a night. Granted there's no pool or some of the other amenities of the con hotel, but it's one of my card hotels (I collected a whole wad of hotel 'frequent stayer' cards last year with all the perl foundation travel, along with a few frequent flyer cards. If you do any travel I'd recommend doing this) so the points'll go on the account. Who knows, maybe some day I'll manage an actual vacation. :)

This place is on the river, just up the park from the conference hotel. It's one of the nice things about Portland--if you've spent any time in any other city, things seem so much closer, as the blocks are extraordinarily short, coming in at about 200 feet each. That's nice, since you look at the map and see you're 10 blocks away, but not actually all that far. The waterfront park is also very nice, and the hotel's right on the end of the MAX light-rail line, so getting there from the airport will be pleasant. (Which, as we're arriving at night with two kids, will be A Good Thing)

Now all I need to do is put in an order for some TriMet passes. I'm not sure if we'll be there long enough to make it worth getting an actual pass, rather than individual tickets, but since Karen'll be zooming around the city with kids in tow, passes are so much easier. Not to mention the fact that Peregrine will be tickled to have his own pass, and TriMet can certainly use the cash...

Posted by Dan at 01:53 PM | Comments (1) | TrackBack

May 19, 2003

RSS Feeds fixed up

Seems that I had some antique RSS templates in place, from an old (probably the first installed) version of Movable Type. I've upgraded to a new set of templates, from the instructions on feeds.archive.org. Now there are brand-spanking-newish 1.0 and 2.0 templates in place. This has switched the .rdf file from a 0.91 to 2.0 format.

What's this all mean? Beats the heck out of me, but if it causes anyone problems, let me know. (I see that the templates are full-content templates, so the feeds are a bit larger than they used to be)

Posted by Dan at 10:54 AM | Comments (1) | TrackBack

May 18, 2003

So who the heck do you tell?

I'm in what seems to be an odd situation. Well, odd for me at least.

I'm getting peppered with virus mail. Now, this isn't at all unusual--I get a lot of virus generated mail. Goes in waves, of course, but it's not too unusual for me to get 30-40 a day, with a normal day being more like 10-20. SpamAssassin catches almost all of them, so they just sit in my SPAM folder on the server waiting for me to pop over and clean 'em out along with all the other dreck that gets pounded my way.

SpamAssassin, along with Vipul's Razor, seems pretty darned good at catching these things, so I generally don't pay them any mind. (I'm also running Eudora on OS X, so it's not like I'm vulnerable to these damn things anyway) Today, though, three got through in relatively rapid succession, all with a spoofed "from" of "support@microsoft.com", and all from the Netherlands (according to the headers). That was unusual enough that I decided to pop over to both MessageLabs and Symantec's website and see which one it is. As far as I can tell, no joy. Nothing matches the characteristics of this thing.

The sensible thing to do, once a check of the search databases and a strings on the decoded attachment turns up nothing, is to report it to the AV folks, right? I mean, my e-mail address has been plastered all over everywhere for more than a decade (yes, dan at sidhe dot org has been active since April 1993), I haven't made any real attempts to spoof my address anywhere, and I generate far too much e-mail, so it doesn't seem too arrogant to assume that on occasion I'll be one of the first people pinged by a new virus. I wouldn't expect it to be a common thing, but neither does it seem unlikely enough to discount.

Given that, I go searching for an e-mail address to submit the message to. A quick pine bounce should send it on, headers and payload intact. Or it would, if I could find where to send the damn thing. A search of the MessageLabs website turns up nothing. (Matt Sergeant, who's active in the perl community, works there, so I figured I'd try them first) A search of the Symantec website also turns up nothing. I could ping some people I know directly, but... I don't think so. Seems an inappropriate use of personal e-mail addresses, and besides, by the time they get it and deal with it likely they'll know by some other mechanism.

Still, it'd be nice to have some way to know. Know that it is, in fact, a known virus, or is something new, and if it's new know that someone'll deal with the damn thing. The sooner the better since, while I'm not vulnerable, I still have to deal with the fallout of a zillion virus messages. Bleah.

Update: On the off-chance someone actually wants a copy, I did a save to a new mailbox in pine and put the results here. Complete with internal folder bits, but chop those off and it's there, in all its glory. SpamAssassin has processed it, hence its headers. I don't have a fully unprocessed version.

Update 2: Well, it turns out not to matter. Whatever this thing is, it's wide-spread. As of 7:30AM EST today, I've gotten 131 of the damn things, and more to come I expect. Notifying anyone wouldn't have stopped that. And yes, I do now have a procmail recipe in to trash these on receipt

Update 3: Turns out to be the Palyh virus. Some day I'm going to sit down and figure out how much data got slung across my wires here just transporting virus traffic...

Posted by Dan at 06:14 PM | Comments (1) | TrackBack

API suckitude of the first order: Perl's magic from C code

So, I'm working on my slides for my "Writing Perl Extensions in C" class, which I'm giving at OSCON this year, and for the New York Perl Seminars group this month. (the evening of Tuesday May 20th, FWIW) Now, this isn't a big deal--I've been writing XS code for years, and it isn't that tough. Perl's API is primitive, dodgy in spots, and mostly undesigned, but it's not that big a deal, and no worse than other libraries I've had to use.

Since I really hate courseware that leaves off important details, I figured I'd better poke around in the docs a bit to refresh my memory about magic. Magic, in perl 5, is that stuff that lets you write code that intercepts read and write requests to a variable. It's used to implement tied hashes/arrays/scalars, lvalues returned from substr or vec, the %ENV hash, and a few other things.

Well...

This stuff is an absolute mess. Most of the API actively ignores magic, though not all of it. The docs for parts of the API says that it does handle magic is actually incorrect. And nowhere is it particularly well emphasized that you really do need to pay attention to magic. It's... well, it's bloody annoying is what it is. To write proper code you actually need to scatter a lot of SvGETMAGIC and SvSETMAGIC calls thoughout it, to make sure that magic, if it exists on something, is properly triggered. Except when you don't, because they happen implicitly. And in those cases where you know no magic is involved.

It doesn't help that the docs are horribly scarce here, and not even the one mostly definitive work on the subject (Extending and Embedding Perl by Jenness and Cozens) doesn't touch on magic at all. I'm currently not sure what the right thing is, nor how many modules on CPAN get it right through sheer happenstance or good choice of defaults by perl. That makes me... nervous.

It's enough to tempt you to add little "This is somewhat more complex with magic" footnotes all around. Or rewrite it entirely. And since I'm doing the latter, I might do the former too. Unfortunately there's no time to do the proper research for the first runthrough, and possibly not in time for the materials turnin for OSCON, so there may well be good reason to take notes in class...

Posted by Dan at 04:11 PM | Comments (1) | TrackBack

May 14, 2003

Registers vs stacks for interpreter design

I was pointed at the mono list (something I generally avoid through sheer lack of time, and the rise in blood pressure it usually evokes) the other day and went reading through some of the discussion on getting ruby ported over to .NET. There's a whole lot of that discussion that I'm going to skip, but one of the things that came up was the eternal "register vs stack machine--which is better" question.

Now, for Mono, of course, there was no question. It's a stack machine and they had absolutely no choice in that. Doesn't make any difference what they wanted, they were copying .NET, and .NET is a stack machine. Which doesn't change the validity of the question, though it does shift who you should really ask about it.

There is no real doubt, it's easier to generate code for a stack machine. Most freshman compiler students can do that. Generating code for a register machine is a bit tougher, unless you're treating it as a stack machine with an accumulator. (Which is doable, albeit somewhat less than ideal from a performance standpoint) Simplicity of targeting isn't that big a deal, at least not for me, in part because so few people are actually going to directly target it--I mean, come on, how many people do you know who actually try to write a compiler for something anyone would ever care about? The numbers are small. The other issue there is that many of the folks with compiler knowledge already are comfortable targeting register machines, as that's what all hardware CPUs in common use are.

But lets, for a moment, consider the two main cases of execution. First, there's interpretation. This requires very little in the way of knowledge of the target machine, and something that Parrot must do. (We have a very wide range of machines we must run on and, no offense, we're probably never going to write a JIT for the Cray Y-MP) There's no real runtime cheating that one can do, as almost all the runtime cheating requires generating native machine code, and for this case we're not doing that.

In both the stack and register case, accessing a register requires an indirect memory access and at least one math operation, either addition or subtraction. For a register machine you're adding the register number to the register file base address to get an effective memory address, and for the stack machine you're loading indirectly from the stack pointer address, then incrementing or decrementing that pointer and storing the difference. The register case saves in memory activity a bit, as there's no need to store the result, while the stack machine has to store the result, but on the other hand the register machine needs more bandwidth to get the register numbers into it, something that's implicit for the stack machine. Whether the additional bandwidth offsets the write is up in the air and system dependent, as it depends on cache access size (the registers and register pointer for the RM, and the stack pointer and top few stack entries for the SM, will be in L1 cache) and sync time--memory writes, in an SMP system, require a bit of cache coherence maintenance, which will sometimes burn a nanosecond or two.

The interpreted case isn't one that .NET really cares about, which is fine, as .NET bytecode is very obviously designed to be JITted. It's the only way to get anything close to adequate performance from it. This isn't a dig at the .NET folks--I actually think it's a very sensible design decision, given the very limited range of machines (x86 and maybe Itanium) they personally were going to care about. Parrot does need to consider it, which arguably hampers us some, but on the other hand it means we don't need a team of Highly Paid Professionals to get us up and running at reasonable speed on some random processor. (And on the third hand, there justaren't that many different processors any more. Unfortunately)

In the JITted case, things are a little different. And better for parrot, oddly enough. (Surprised me) Now, when we JIT (and I should do a WTHI entry on JITting) we turn the interpreter's bytecode into native machine code. With JITting, we need to consider both the naive case, where we're doing a mechanical translation, and the clever case, where we're doing some analysis of the incoming bytecode.

There is, first and foremost, a general win with JITting. Just inlining the code for the different machine ops buys a fair amount, as you cut out the function pre- and post-ambles on the called op, and the parameter setup and teardown on the function call. If you have small opcode bodies this can be a considerable win. (Factor of 10 type win, which is a lot)

In the naive case, the stack machine still twiddles its stack pointer and accesses all its data indirectly. It has no real option, as we're being naive. That pointer's probably moved from the interpreter structure to a register, which is good, though it might've been in one already. (Probably was) It does get the general JIT win, which is a Good Thing, and quite the speedup. Still, indirect memory access takes a bit of time, though far less than it used to. (These days it might put a stall entry in the pipeline, which may be used by other ops in the stream, where in the old days an indirect memory access might cost you an extra cycle or two)

The register machine, though... The register machine has suddenly gone from indirect access to direct access to the memory, and the instruction stream has gotten denser. Remember, the register number is a constant. Also remember that the registers themselves don't move. And consider that in the common case you have only one interpreter, so there's no reason to not JIT the code just for it. Which adds up to turning register numbers into absolute memory addresses. Not even indirect ones--no "base pointer plus 4" or anything, but direct "load or store from absolute address X". The register file doesn't move for an interpreter, so we know when JITting where each register is, so we just encode it in the instruction stream. Yeah, it does mean that we need to re-JIT for each interpreter, but as I said the non-threaded case is the common one for us. Still, it's all memory access.

In the clever case the overhead for a stack machine is lessened some. The incoming instruction stream can be analyzed, so a sequence like:


push a
push b
add

can be turned into "put a in a native register, b in a native register, add 'em, and put the result back on the stack". Which is what a basic optimizing compiler does, and what a JIT in the clever case is--an optimizing compiler. There's less stack pointer twiddlidge in this case, as you can skip twiddling it for everything but the final push, and if that intermediate result is used somewhere else and not stored you might be able to dodge that stack access too.

There's a good win for the clever case for register machines, though. (Unfortunately we lack the personnel to do it for Parrot--we have the people who can, just not the cash to actually let them do it and still eat) to get that win for the stack machine you need to analyze the instruction stream and pick out what the temporaries are. In the register machine case, those temporaries are explicit--that's what the registers are, a set of temporary values. More to the point it's easier to track register usage in the common cases than it is intermediate stack results. (Not, mind, that either is tough, but since JITting is done at runtime, every millisecond counts)

Finally, some of the compile-time optimizations can be done at bytecode generation time for the register machine, where they have to be done at JIT time for the stack machine. Consider this:


c = a + b
d = a + 4;
e = b * 3;

Five variables, three statements. We'll assume, for the moment, that all the variables are 'objects' of some sort, and not just plain ints. What's the stack machine instruction stream?


push 'a'
getvar
push b'
getvar
add
push 'c'
storevar
push 'a'
getvar
push 4
add
push 'd'
storevar
push 'b'
getvar
push 3
multiply
push 'e'
storevar

19 ops, 9 parameters. And for the register stream?


getvar R1, "a"
getvar R2, "b"
getvar R3, "c"
getvar R4, "d"
getvar R5, "e"
add R3, R1, R2
add R4, R1, 4
mul R5, R2, 3

8 ops, 19 parameters. (oddly enough, one fewer total thing in the instruction stream than in the stack machine case, though not a win as such for parrot as each think for us is 32 bits, where each op for .NET is 8 bits, so we're using 108 bytes to .NET's 55 (assuming parameters are 32 bits, which may be inaccurate for .NET--the small ints might be smaller, but we can make them all 2 million to make sure they're 32 bits if you really want)) And note that we only fetch the pointers for a, b, and c once, and don't have to do an explicit "put the stack top into variable" op that a stack machine does. (Though you could certainly put the address to store the result on the stack as part of the add, rather than putting the result of the add on the stack. .NET doesn't do that, though)

In the naive case, we JIT and execute less than half the ops that the stack machine does, and have a denser instruction stream for it. In the clever case, the JIT for the stack machine can figure out that a, b, and c are used multiple times and skip the multiple fetches, certainly, but that figuring needs to be done at runtime, every time the program is run, where in the register machine it's done at compile time, and every time the program is compiled. Generally we don't compile more often than we run, and usually less. (Often much less)

Now, there are some vagaries of Parrot and .NET specifically to contend with--with enough effort the fairly obvious inadequacies of either system can be dealt with, and I'm sure someone "Hi, Miguel!) will chime in with specifics. Parrot's got a good win over .NET for interpretation, as that's what we're designed for, while .NET's got a simplicity win of sorts in the JIT case, and, on Windows at least, has a huge personnel win over parrot. (Rag on Microsoft all you want, their compiler guys are first rate) Mono's got a smaller win, though still a win there.

Still, it ought to be fairly obvious that register machines aren't the vile and horrible things people make them out to be.

Posted by Dan at 09:01 AM | Comments (7) | TrackBack

May 12, 2003

'Twas a lime weekend

Last sunday was Mother's Day here in the US, and that means a weekend of cooking. Which is a grand thing, as I rather like cooking, and the kids do too. As part of it, I rediscovered lime as a flavor. I'm definitely thinking it's underused, especially in deserts. Yeah, you can get that bright green "lime" stuff, but... yech. Lime juice, both from traditional limes and key limes, is more a pale yellowish green.

Anyway, combined with a nice buttercream frosting base and a white chocolate cake, the results are just outstanding. (For the record, the cake was a white chocolate layer cake with a key lime curd filling and key lime flavored buttercream. Mmmmm, good!) Best served warm, as the volatiles in lime juice that give it that nice flavor don't volatize well at 'fridge temperatures, so a cold cake tastes fairly flat, but when warm...

Scallops work really well when marinated with lime and garlic too, though you have to be careful there to not cook the scallops in the juice. (Both lemon and lime juice will curdle the proteins in scallops more or less the same way that heat does, so if they soak too long they'll be the consistency of cooked scallops, and if you then cook those you end up with lime-flavored rubber balls. Definitely not good eats, to steal a phrase)

And there's something to be said about any holiday where giving Munchkin is considered entirely appropriate.

Posted by Dan at 05:38 PM | Comments (3) | TrackBack

What's the point of continuations and CPS?

Cedric (it's amazing who you can find linking to you via Technorati) noted, twice, that while he understands continuations and CPS, he doesn't much see the point. And that brings up a good question--what is the point? What does it buy you?

Well, with exposed continuations, it's reasonably simple. If a language has them, you can put together any sort of control flow mechanism you want. Feel like having a perl-style foreach that iterates over a list of variables and don't have one? Build it. And for the language implementor they're pretty nice too, as once you get them done you don't actually have to implement any other form of control structure, just convince the compiler to emit the right variant of continuation use under the hood. (Of course, it's not necessarily the most efficient way to build things, but it does get you going quickly, especially if you're not that fond of building language engines)

Doing CPS, though, can make implementation a lot easier, at least for some languages.

Now, let's be up front--using CPS for a really low level language like C or C++ would be counterproductive, and that's fine. Don't use it there, as it'd be kind of stupid. (Not that you couldn't, mind, just that there'd be little point given the thrusts of the languages) There are places, though, where going with CPS makes things easier, less error-prone, and potentially more flexible for backwards/forwards-compatibility.

Consider, for a moment, Parrot. This is a VM designed to run Perl, Python, Ruby, and suchlike languages. We have lexical variables, which means we have to swap lexical scopes in and out as we make function calls. We have lexically scoped nested global namespaces, which once again need to be swapped in and out as we make function calls. We've got dynamically loaded opcode functions and, more importantly, scoped opcode function tables. (Which means that two different pieces of bytecode may both use opcode # 1034, but it means something different in each piece. Long story, makes sense, ask later) We may well have scoped interrupt levels, though probably not. We also have quite a few stacks.

All that stuff needs to be saved and restored. We do caller-save, so the code making the function call will have to save off the important state. The called function isn't off the hook--it still needs to track the stacks, of which we have quite a few, and make sure that there's nothing extra on them.

Or... do we?

That's where CPS comes in, and makes things just a whole heck of a lot easier. At least for parrot.1 Consider all the saving and restoring. Compilers have to emit that code. Not, in itself, a big deal, but still, it's code that needs to be spat out. More importantly, consider the possibility for future expansion. What if we do decide to have lexically scoped filehandle filters, say. (Why? I dunno. Work with me here) If we use CPS, and more importantly if we have a single instruction to build the continuation object, then we're essentially future-proof(ish)--it doesn't matter that we're running old bytecode that doesn't know anything about lexical filehandle filters, the engine does and the CPS thingie builder will Do The Right Thing.

It also makes life in the subroutine easier. Since the stacks need restoring, that means we need to track how deep we are and pop off the extra, or have some sort of "mark for later undo" function. If we just do continuations it's part of the call--we're already passed in the mark we're interested in. (And, going CPS means we're probably not being very stackish, more going with a frame system, except for the control stack that needs to track runtime exception establishment. Which is, in itself, a bit of a pain with continuations, but that's a separate issue)

So, the annoying stuff is boiled down to two seemingly harmless instructions--makecontinuation and invokecontinuation. Makes emitting code (both by hand and machine) easier, condenses the code stream, and generally makes things more clean from the outside. Yeah, there are continuations, but they're well-contained.

The one big downside is that it might make python emulation more difficult, but I think we can work on that one. Pie is, after all, involved.

The short answer to "Does this mean much to me" is "No, not unless you're writing an interpreter engine, or generating code targeting one that uses CPS." Which is fine. It's nice to have things you don't have to bother with. :)

1 That is, assuming we do CPS, which we don't right now. Though that may well change.

Posted by Dan at 12:21 AM | Comments (4) | TrackBack

May 07, 2003

Clint Pierce is my hero

Clint Pierce is my hero. Below, hopefully not included inline here, is an example of how to clear the screen, under Windows, in Parrot assembly. And yes, I know, printing an escape sequence would be easier, but hitting the Win32 API directly, with no extensions or windows-specific code, is just, well, damn cool.

I shall have to do something with OS X and Cocoa, I expect, though that will await objects...

        new P25, .PerlHash
        bsr MAIN
CONSOLE_SETUP:
        loadlib P24, "kernel32.dll"
        dlfunc P0, P24, "GetStdHandle", "pi"
        set I0, 1
        set I5, -11
        invoke
        set P25["kernel32"], P24
        set P25["handle_as_*"], P5
        new P0, .PerlHash
        set P25["console"], P0
        dlfunc P0, P24, "GetStdHandle", "ii"
        set I0, 1
        set I5, -11
        invoke
        set P25["handle_as_int"], I5
        ret
CONSOLE_INFO:
        set P24, P25["kernel32"]
        dlfunc P0, P24, "GetConsoleScreenBufferInfo", "iip"
        set I5, P25["handle_as_int"]
        new P5, .UnManagedStruct
        set P5, 32
        set I0, 1
        invoke
        set P0, P25["console"]
        set I0, 0               # dwSize.X
        bsr UMS_GET_SHORT
        set P0["xbuf"], I1
        print "X is "
        print I1
        set I0, 2               # dwSize.Y
        bsr UMS_GET_SHORT
        set P0["ybuf"], I1
        print "Y is "
        print I1
        print "\n"
        set I0, 4               # dwCursorPosition.X
        bsr UMS_GET_SHORT
        set P0["curx"], I1
        set I0, 6               # dwCursorPosition.Y
        bsr UMS_GET_SHORT
        set P0["cury"], I1
        ret

CONSOLE_HOME:
        set P1, P25["console"]
        set P24, P25["kernel32"]
        dlfunc P0, P24, "SetConsoleCursorPosition", "ipi"
        set I0, 1
        set P5, P25["handle_as_*"]
        set I5, 0
        invoke
        ret

CONSOLE_CLEAR:
        set P1, P25["console"]
        set P24, P25["kernel32"]                       # 87655
        dlfunc P0, P24, "FillConsoleOutputCharacterA", "iicilp"
        set I0, 1

        new P5, .UnManagedStruct
        set P5, 8

        set I5, 0                       # Coords
        set I1, P1["xbuf"]
        set I2, P1["ybuf"]
        mul I6, I1, I2                  # Length
        set I7, 32                      # Char
        set I8, P25["handle_as_int"]    # Handle

        invoke
        ret

DUMP_BUF5:
        set I0, 0
DUMP_BUF_LOOP5:
        eq I0, I1, DUMP_BUF_END5
        print I0
        print " "
        set I2, P5[I0]
        print I2
        print "\n"
        inc I0
        branch DUMP_BUF_LOOP5
DUMP_BUF_END5:
        ret

WIN32_ERROR:
        set P24, P25["kernel32"]
        dlfunc P0, P24, "GetLastError", "lv"
        set I0, 1
        invoke
        print "Error number: "
        print I5
        print "\n"
        ret

        # P5 UnManagedStruct
        # I0 offset in UMS
        # I1 return
UMS_GET_SHORT:
        pushi
        set I2, P5[I0]
        inc I0
        set I3, P5[I0]
        shl I3, I3, 8
        add I3, I3, I2
        save I3
        popi
        restore I1
        ret

MAIN:   bsr CONSOLE_SETUP
        bsr CONSOLE_INFO
        bsr CONSOLE_CLEAR
        bsr CONSOLE_HOME
        end
Posted by Dan at 04:42 PM | Comments (1) | TrackBack

What the heck is: Continuation Passing Style (CPS)

Before you go any further, make sure that you understand, at least a bit, about continuations. I wrote about them earlier, and there's plenty of material around the internet to confound and confuse you on the subject. (And bemuse and horrify, but that's the 'net for you, I suppose)

So, we'll assume you understand continuations. As a refresher, they're a sort of super-closure, one that remembers both variables and the call chain. When you take a continuation you get a thingie that remembers all the lexicals that were in scope when it was created, as well as remembering the call sequence you took to get there. (And if the places you were had lexicals in scope, the continuation captures them too, since they'll need to be put back in place again if you use the continuation)

Now, consider the humble function call:

foo(1, 2, 3);

What happens?

Well, in a standard system, when your code gets to the function call it does a few things. First, it pushes an address onto the stack. Then it puts the parameters either on the stack or into registers, depending on how many registers your CPU architecture had handy when the calling conventions were established. Finally it calls the function.

When the function does its thing it yanks the parameters out of wherever they were (on the stack, or in registers) and then it does what it's supposed to. When the function is done and has to return, it puts its return value somewhere (if it has one) then pops the address off the top of the stack and jumps there. No biggie.

Got that? Caller pushes the return address, the address of the instruction to execute when the function call is done, onto the stack. Caller then calls the function. Function does its thing and, when its done, removes that return address from the stack and goes there.

It's actually pretty simple, and a lot of hardware actually has built-in support for this function calling mechanism--there's the jsr (and sometimes bsr) instruction that pushes a return address onto the stack and jumps into a subroutine, and the ret instruction that pops an address off the top of the stack and jumps there. Different CPUs may have different names for these operations (and some don't have bsr, just jsr) but they do the same thing, and they make subroutines easy for the assembly language programmer. Unless you need to pass parameters in and out on the stack, in which case they get really annoying (since the parameters are beneath the address on the top of the stack) but that's computers for you.

Now, you can probably see a potential issue here. "What," you might say, "happens if I make a massively nested and/or recursive set of sub calls?" Well, bucko, you blow your stack and crash. Or scribble over heap memory, which is arguably worse. Not fun, as you might expect, though in these days of 32 and 64 bit address spaces it's not as common an occurrence in non-threaded programs as it was back in the 8-bit days, when you had maybe 48K of RAM, total. (And, of course, we had to walk barefoot five miles up hill in the snow to pop the tape with our program into the 300 baud cassette loader. Ahem. Sorry, old fogey moment) Still, it can happen, and in circumstances where your stack is much smaller, such as when running with threads (you often get only 20-30K of stack space per thread) it can be darned common. It has the twin advantages of hardware support and conceptual simplicity, though.

The stack method of calling is a bit of a pain for some uses--when you had to use the stack to pass parameters you end up twidding what entries are where so you don't prematurely pop off the return address, or cover it with the return data.

You may be thinking "But what about lexicals? How does this interact with lexicals?" The answer is.... it doesn't. This is, remember, an old method of doing things, and it predates lexicals. (Yes, I know, it doesn't really, but it predates lexicals in common use. And no, Lisp at MIT doesn't count as common anything) Generally if you want to save your current lexical state, you push a pointer to your lexical pad onto the stack before you make a function call, and restore it after the call is done. If you're working with a language that doesn't do lexicals, as most don't, it's not a problem since they're just not around to worry about. (The problem with lexicals is that they can't be allocated on the stack if there are closures or potential closures being taken)

Continuation Passing Style, or CPS for short, is completely different. With CPS, no address is pushed onto the stack before a function call is made. Strictly speaking, the whole jsr/ret scheme isn't used at all. What happens with a simple function call like:

foo(1, 2, 3);

is a little different than what happens in the stack style. First, a continuation is taken, one that resumes just after the function call. (Remember that, in addition to the lexicals and call stack, continuations have to hold the place you are going to go if they are invoked) Then the parameters are put somewhere, often on the stack. One of the parameters, generally unspecified, is the continuation that was just taken. (This is the continuation passing part) Then we just jump to the start of the function.

When the function executes, first it takes the parameters and puts them wherever it needs to. Then it stores the continuation someplace. The function does its thing, whatever that is. When it's finished, it generally puts its return parameters someplace, and invokes the continuation that was passed in. Since that continuation, conveniently, puts us in the calling function at the spot right after the function call was made, with its environment intact--just like in the stack style.

The cool thing about using a CPS for function calls is that it makes taking a continuation much, much easier. When taking a continuation, you only have to take a snapshot of the current function/sub's lexicals and control stack--you don't have to care at all about the function's caller, or the whole stack or anything. Why not? Because when the function you were in was called, its caller conveniently took a continuation and passed it into you. You don't have to look outside your own routine when taking the continuation because the caller already noted that stuff for you. That makes taking a continuation much faster and simpler. It can even be optimized some--you're taking note of just the info in your own sub/function/method, so the compiler knows exactly what is and isn't important, so it can ignore anything that you're not using.

CPS also makes what're called "tail calls" (and tail recursion) much easier and faster. I'm going to talk about them in a later post, but consider this:

sub foo {
# Do some stuff
return bar();
}

That is, we have a sub that does stuff and, as its last two actions, calls a function and then exits with its return value. And also remember that foo got its caller's continuation passed in. And that we pass in the continuation of where the called function should go when it finishes. Which all adds up to, in this case, foo not bothering to take a continuation at all, just passing in the continuation it got into bar. Which is a nice optimization. (And in cases of recursion, avoids allocating a continuation structure per call, which if you're looking for the millionth prime number can be a not inconsiderable savings)

Unfortunately, CPS does have a down side. Two, actually. (Ignoring the whole "Aaaah! Continuations! Make the pain stop!" issue, since they're really not that bad)

The first thing that springs to mind is return values from functions. Since invoking a continuation is supposed to put everything back the way it was--stack and lexicals--you'd wonder where the called function is supposed to put them. Can't go on the stack, as in the stack system, since we put the stack back. Generally in a CPS system there's a single return value, and it goes into a register. (Registers, generally, aren't restored) This makes returning multiple values a bit tricky, though most languages that do CPS don't do multiple return values. But, then, neither do most languages that do a stack system. CPS is a bit of a hindrance for languages that do have multiple return values, since then you need to either have a special area that isn't restored on continuation invocation, or build a little stack of return values and pass back a pointer to it as your return value. (If you're an old hardware wonk you might wonder what you'd do in the case of a system, such as the 6502, where the registers are both too small to hold the largest data element the processor can handle and are too small to hold a pointer (all the registers are 8 bits, a pointer is 16). The answer is "You can't use CPS at all easily there. But you can play M.U.L.E., so it's a net win")

The second problem with CPS is that it involves continuations. Not in a terror-inducing way, but rather in a speed hit way. The one downside to supporting continuations is that it means you can't use the stack to hold both control information and data. So things like C's auto variables can't be put on the stack, which slows things down as you need to allocate a frame for them somewhere out of heap memory. While not slow, it's definitely much slower than just incrementing/decrementing (depending on which way the stack goes) the stack pointer by a few dozen words. If you're shooting for speed, well, you just missed a bit. If that's not a problem, or the semantics of the language you're using requires the heavier-weight variable allocation system (or, like Lisp, has so few variables in use that it doesn't matter much how long it takes to allocate them) it can be a useful win.

Still, in a register rich system CPS is definitely pretty darned cool I'll admit, after this, I'm almost tempted to shift Parrot over to a full CPS system. Almost....

Posted by Dan at 09:30 AM | Comments (4) | TrackBack

May 06, 2003

We got categories!

Well, OK, category, but still...

There's no RSS or anything for it (and if anyone knows how to do this for Movable Type, let me know and I'll do it) but I added in a category for the "What the heck is" series of posts, for folks that don't much care about the rest of the stuff. It's on the sidebar, cleverly titled What the heck is:. The existing posts are already in there, and anything new should go in it as well.

At some point I may go and add some more categories, but I figured this, at least, would make it easier for the folks tracking the explanatory stuff.

Oh, yeah, for the record the next post will be on either garbage collection or continuation passing style, whichever I finish first.

Posted by Dan at 03:31 PM | Comments (2) | TrackBack

May 04, 2003

What the heck is: The pending topic list

Figured I'd add this as a list of the topics I've got on the list to talk about at some point. If there's something you'd like that isn't here, add it in as a comment, please.


  • Tied variables
  • Tail calls
  • Garbage collection and Dead Object Detection (Yes, they are separate things. Really)
  • ASTs and Interrupt routines
  • Async I/O
  • Threads
  • Registers
  • Exceptions
  • JIT
  • CPS (Continuation Passing Style)

Not, mind you, necessarily in this order. The discussions will be both an explanation of what the thing is as well as why you'd use it. (Which is why threads are on the list, for example, though folks probably know what they are)

Standard "I'm a grumpy engineer and hardware guy" perspective applies, of course. :)

Posted by Dan at 04:28 PM | Comments (10) | TrackBack

Fun with Blogshares

I ran across Blogshares a while back (as you can see from the banner icon thingie over there on the right) and, as I was going through a "Who's linking to me" phase, I figured I'd join up. I've been watching it on and off, though I'm not really playing--stock market simulations really aren't my thing. Call me an amused observer.

Reading the news, it looks like they're working on a ping system. Same as the weblogs/blo.gs XML interface (which is good, so no custom code is needed, just add in the URL of the reindexer to the ping list) and you're golden--reindexing more or less after you've updated the blog, rather than at some interval when the spider gets around to looking at you.

Now, if there was just a way to disable their spider entirely, so it doesn't waste its time and resources polling regularly, or at least set to a really long refresh cycle so it does check if you break something on your end...

Posted by Dan at 04:21 PM | Comments (0) | TrackBack

May 03, 2003

Summer conferences and classes

One of the nice things about summer is that it's conference season. I don't go to that many, but I get to a few, which is always nice. While it can be terribly draining, I still think travel's cool, and I really enjoy going places I've not been before.

This year, I'm doing OSCON and YAPC::EU.1 As part of that I'm doing tutorials and classes to help pay for it all. At OSCON I'm doing a "Writing Perl Extensions in C" class, and for YAPC::EU I'm teaching a three-day Beginning Perl class. Plus talks--a state of parrot talk for OSCON, and some as yet to be determined talks for YAPC::EU. Unfortunately the timing's a bit dodgy, so I get home from OSCON on a Sunday, pack up, and fly out to Europe on Monday. Not, mind, that I'm complaining--I get a week in Portland and nearly two weeks in Paris and, let's be honest, who'd complain about that?

Anyway, hopefully I'll see everyone at one place or another. (Alas, this year I won't be making YAPC::NA, YAPC::CA, or RubyCon2003, which is a shame) By all means, please sign up for classes if you want 'em, to make sure that enough people do it that I can actually go. :) (And I do teach classes outside conferences, if anyone wants them)

This does mean that it's slide patch-up time, though. I've got to get my C extension slides done in the next week or so as I'm giving a runthrough of it at the NY Perl Seminars the evening of May 20th, and my Beginning Perl slides and class notes need some fixup and rework so I can get them off for printing for class. Karen's well past tired of me pushing this stuff to the last minute, so I'm trying to get it all done in a reasonable time. We'll see if I manage.

1 That's Yet Another Perl Conference Europe, as opposed to YAPC::NA for North America, YAPC::CA for Canada, and YAPC::Israel. All low cost (less than 100 Euros or dollars, generally), relatively small (~200 or so for ::NA and ::EU, can't speak for the others) perl conferences, originally put on as an alternative to the much more expensive O'Reilly Perl Conference, and later OSCON

Posted by Dan at 01:24 PM | Comments (0) | TrackBack

May 02, 2003

A pleasant side-effect from the sham of federal "tax cuts"

I was listening to the radio on the way home the other night and one of the commentaries on Marketplace was about the disingenuousness of the proposed tax cuts the shrub's pushing for at the federal level. The point that was made was that these wouldn't ultimately reduce anyone's tax load--all it does is shift the burden for collecting the taxes from the feds to the states.

Now, personally I think the proposed tax cuts are a profoundly bad idea, since we're deficit spending. Cutting taxes when you're deficit spending is actually a lie, since taxes must be collected to cover spending, and when you borrow money to spend rather than just taxing to spend you're increasing the amount of money that is spent and will ultimately have to be collected. (Interest and all, and the t-bill rate is generally above inflation by quite a bit, so the real cost does increase) Deficit spending also makes the tax structure more regressive (or less progressive, depending on your ideology) since the government is paying the wealthy, both corporations and individuals, interest. (The poor, in my experience, don't generally have a megabuck or two to drop on 30 year tbills)

So, tax cuts while deficit spending is bad. Government deficit spending itself is generally bad, though there are occasions (World War II comes to mind) where it's not as bad as the alternatives, but generally bad, and to be avoided. Tax cuts while not deficit spending are OK, though if there's a debt to be serviced generally we're better off doing so rather than cutting taxes. The sooner you pay off an interest-bearing debt the less you ultimately pay, which saves us money in the long run. And the US definitely has a debt to service. (Do please note that the rules for debt and deficit spending are different for public and private agencies, since public agencies are generally not allowed to make a profit, so the potential for long-term revenue generation is very different)

Anyway, the point the show made was that the "tax cuts" won't cut taxes, really. They'll cut federal taxes, but the states will end up having to raise taxes in turn to cover the difference. Much of what the federal government spends ultimately goes either directly to the states (for block grants), or goes to programs funded in conjunction with the states (such as medicare and medicaid). If we cut taxes we cut grants and disbursements to the states, but the states can't cut the services provided under the programs, either by federal law or by public outcry. So the states will end up paying the difference, and will have to raise taxes to cover that. It's not like there's a whole lot of spare cash locally.

Why is this good? Well, really it isn't, but sometimes you get to make the best of things. In my case, I live in Connecticut, and last I checked we had the highest per-capita income in the country, and have the town with the highest per-capita income in the country. (Though that wobbles back and forth with Grosse Point, Michigan) We also have three of the ten poorest cities in the country (including the one that Yale is in) so it's not like we don't have balance issues. But we also get back only about 60% of the funds we send to the feds, including the defense contracting that United Technologies and Electric Boat do.

See where this is going?

If the state raises taxes to match the feds lowering of taxes, there'll be a lot more money in the governments to fund schools, poverty abatement programs, farm subsidies, drug treatment programs, mental health and public health services, and roads. Plus we'll be in a position to tell the feds to go jump when they make demands and use funds as leverage. Much of the power the federal government has over the states is due to the federal education and highway funds, but if they're cutting those anyway, and we're raising taxes to cover the loss (potentially at a net lower tax rate, since we really need to raise taxes only about 60% of what the feds drop...) well, screw them. Which we might do anyway, as one of the five best schools in the state is about to be cited under shrub's "No Child Left Behind" act and potentially taken over since it's so much worse than schools in, say, appalachia.

Amusingly, this shifting of taxes will tend to hit the conservative states that are pushing for the tax cuts hardest. States like Louisiana and Iowa see net wins in federal tax collections, which means that they need to raise local taxes more than the federal tax cuts to keep things stable, or see programs cut.

Now, am I a big fan of all this overall? No, not really. I'm one of those odd people who doesn't mind paying taxes, since I'm rather fond of (USA PATRIOT act-free) police and fire services, federal parks, interstate highways, and air-traffic control systems, and I'm just fine with wealth shifting to get the dead-poor and horribly screwed a chance to make things better at the expense of those of us with more cash. (And yeah, that includes me, though I'm far from rich. Fair is fair) But if the country's demanding, either directly or through political apathy, that all that gets screwed country-wide, well, fine. You wanted it, you got it. We can afford to offset the difference. Can you?

Posted by Dan at 06:00 PM | Comments (5) | TrackBack

Drooling over the new iPods

Apple, as part of its whole "Apple Music agreement? What Apple Music agreement" campaign, has announced and released new 10, 15, and 30G iPods. (Presumably Randall just bought a 20G version) And, like the rest of the world, I'm drooling over them.

The 30G, particularly, is catching my eye. Not only would it mean I could get music in the car (the one big deficiency of the iBook is its forcing sleep with the lid closed, making it unsuitable as a mobile jukebox) and have a handy backup gadget around, but I could finally watch movies on the plane. This iBook is nice, and with the two batteries I can generally work on a full cross-country flight (though I think one of my batteries is getting creaky and I may have to add a third to be sure) since they're good for between two and three hours each under my normal workload.

What this thing can't do, though, is show a full movie on one battery charge. Or at least one modern movie, as I can usually get a 70-75 minute one on it, which is enough for most of the really bad black and white 50's B-grade atomic monster movies I like. But for something newer, like Pleasantville or Spirited Away? No joy. The DVD drive just sucks down too much power for that to work. No complaints, mind--being able to watch anything on the plane's good, I just stock up on episodic stuff. (Though there are issues there, since a lot of the episodic stuff I have is anime, and the social norms are different. While I don't have a problem with, or even care about, casual nudity and such where it's appropriate, or fan service where it's not too bad, I'm a bit uncomfortable displaying it in public. Well, except for Fake, because it's sometimes fun and/or necessary to make people squirm a bit)

Anyway, what does all that have to do with this? Well.... a 30G iPod is really a fancy 30G hard drive. That's space for at least two, if not five or six, movies, in full quality. The iPod can run for quite a few hours feeding data, and the firewire ports take up a lot less power to drive than the DVD drive (no surprise, as there aren't any moving parts), so I ought to be able to take a few movies with me to watch on the plane. Cool!

The only problem is the price, since at $998 (what, you think my wife'd let me get just one? :) it's definitely past the impulse purchase stage.

Ah, well, I can dream, I suppose.

Posted by Dan at 05:19 PM | Comments (4) | TrackBack

May 01, 2003

What the heck is: a coroutine

Well, OK, grammatically it ought to be "what the heck are: coroutines" but we'll make do for the sake of easy searching. (I probably ought to see about putting categories in, but I don't know that it's that important)

Anyway, what is a coroutine? Well, a coroutine is a subroutine or function that can pause in the middle and return a value. When you call it again it picks up right where it was when you paused, environment intact, and keeps on going. Coroutines are almost always closures, though strictly speaking they don't have to be--if you had a language that didn't have closures, or lexical variables, you could still have coroutines. (Though generally speaking nobody does that. You could, though, if you were so inclined) Often coroutines are called iterators or generators, but that's not strictly correct--while you can make iterators and generators with coroutines, you don't have to. Nor are coroutines limited to being iterators or generators, as some systems use coroutines to implement threading, and coroutines can make really nifty callbacks.

From the outside you can't tell if a function is a coroutine any more than you can tell that a function is a closure. Your code calls it and gets back a value (or values) and that's all that's important. (Yay, encapsulation)

Like normal subroutines or closures, the first time you call a coroutine it starts at the beginning. If a coroutine hits a return, or falls off the end of the code (assuming you're using a language that supports just falling off the end of a function) the next time you call into the coroutine it also starts back at the beginning. In this regard it's no different from any other subroutine or function. It's when you call yield (or your language's equivalent), though, that things get interesting.

yield, like return, returns both a value and control to the code that called the coroutine. What it does, though, is remember where in the coroutine's code it was, so that the next time the coroutine is called it picks back up right where it was the last time you were in the coroutine.

For example, if you have this code:

  coroutine foo {
    yield 1;
    yield 2;
    yield 3;
  }
  print foo(), "\n";
  print foo(), "\n";
  print foo(), "\n";
  print foo(), "\n";
  print foo(), "\n";
  print foo(), "\n";

prints

1
2
3
1
2
3

Now, you may ask "What about all those pesky parameters? What happens with them?" Well, the answer is "It depends".

Coroutines can certainly take parameters. While they wouldn't be completely useless without them, they certainly would be less useful than they might otherwise be. How languages handle parameters and coroutines can differ, though. The issue isn't passing parameters in the first time you call a coroutine, as that's easy, but rather what happens when you call a suspended coroutine with potentially different parameters than you called it the first time. Languages generally have three different things they do in this case. Let's alter our coroutine a bit:

coroutine foo (int a) {
yield a;
yield a + 1;
yield a + 2;
}
print foo(1), "\n";
print foo(2), "\n";
print foo(1), "\n";
print foo(1), "\n";

The first way is to just ignore passed-in parameters on the second and subsequent invocations. Yeah, it's nice that you called the coroutine with 1 the first time and 2 the second time, but the coroutine was happy with 1 and it'll stick with that. If you happen to call it when the coroutine's reset then the parameters will be respected. So in our example above, it would print "1 2 3 1".

The second way is to remember what the coroutine was invoked with and create a new instance of the coroutine if you pass in different parameters. Whether the language retains multiple versions of a coroutine depends on the language--some do, some don't. Our example would print "1 2 2 3" if the language does retain multiple versions of a coroutine, or "1 2 1 2" if it doesn't. (The change from 1 to 2 as a parameter would discard the old coroutine, as would the change from 2 back to 1, if the language discards on parameter changes)

The third way is to just pass in the parameter and alter the variable that it's tied to on each coroutine entry. (This only works if you're actually using the passed in parameters directly--in the case of perl, where everyone copies them out of @_, you'd probably not see the changes because you copy out of @_ at the beginning of a sub) So our example would print "1 3 3 1".

Which way is right? Well, they all are, really. Each way has its advantages and disadvantages, both from a language comprehension standpoint and an implementation standpoint. Whoever's designing the language decides which way they like best, or which way is doable with their current implementation, or which way grinds the right ideological axe, and goes with that.

One thing to remember is that you can yield out of a coroutine no matter how deeply you're into it. Our examples are really simple, but you could be nested ten or twenty levels of scope deep in a coroutine and still yield out--when you re-invoke the coroutine you'll be dropped back where you were, ten or twenty levels deep, with all your lexicals put back in place. (If you're using a language without lexicals it makes things easier)

Co-routines are definitely very powerful and often are used as iterators, tied to some data structure where re-invoking the coroutine walks through the structure a step, or generators, where each invocation of the coroutine returns the next value in a series. It can be much easier to do both when you can retain both variable and position in code state, depending on the type of iterator or generator. Closures are often sufficient for simple iterators and generators, but sometimes just retaining data isn't sufficient, or if it is sufficient it's quite inconvenient to make sure you retain enough data to pick up where you left off.

The big downside to coroutines is in their implementation. To make coroutines work properly requires relatively complex calling conventions and state keeping, though not really any worse than what you might need if an engine supports continuations, so if you have those it's not much more work. It really wants chained call frames rather than a call stack and doesn't want parameters passed on the stack, amongst other things.

Implementation, though, is a mildly complex thing, and discussion is definitely a task for another day.

Posted by Dan at 04:47 PM | Comments (10) | TrackBack

A pleasant surprise in iTunes 4

And no, I don't mean the green icon either.

Like most of the rest of the Mac world, I upgraded from iTunes 3 to iTunes 4, and installed Quicktime 6.2. I wanted the new AAC encoding stuff, as I'm always up for higher quality (all my MP3s are ripped at 160Kbit) since I like music, and the point of ripping the CDs is so I can have the music handy while not endangering the discs. (I've certainly trashed enough CDs over the years) Being able to buy tracks was a nice bonus as well, though one I don't expect to use for a while. (They don't have the Dudley Moore and Peter Cook sketch "The Frog and Peach" online. I checked. Though they do have Monty Python, and "The Galaxy Song" beckons)

Not too surprising, since if it's something I like and on CD odds are I've bought it already, which leaves all the stuff that hasn't made it to CD yet, and probably not up on the Apple Music site. Though they are encoding straight from the master tapes, or so they say, so some of the good old stuff may make it up. If they could build up a good catalog of early Jazz and Blues stuff, well, that'd be damn cool. And a good thing, since the old magnetic tapes aren't getting any better with age--while MP3 and AAC are both lossy encoding mechanisms, flaking oxide coating has them both beat for loss.

Anyway, while iTunes has been made a touch uglier (No, I didn't think a brushed metal app could look any worse either, but there you go) its sound output seems better. I'm listening to a playlist full of lower-quality MP3s, and darned if they don't sound better than they did with iTunes 3. Might just be me, or the really good coffee today, but it's definitely nice.

Posted by Dan at 04:46 PM | Comments (0) | TrackBack