
Planet Perl Six is an aggregator of select Perl 6 related blogs. The list of contributors changes periodically.
Planet Perl Six provides its aggregated feeds in Atom, RSS 2.0 and RSS 1.0, and its blogroll in FOAF and OPML
Help! I'm getting the error "invoke() not implemented in class 'Undef'" in my large application. What did I do wrong?
You've mistyped a class name which sits inside a namespace.
How am I supposed to figure that out?
I didn't say it was a particularly good error message.
It's like this: if you mistype a class name which is not in a namespace, you'll get an informative error message:
$ perl6 -e 'A.foo'
Could not find non-existent sub A
in Main (file src/gen_setting.pm, line 324)
However, if you mistype a class name which is in a namespace, you will get an uninformative error message:
$ perl6 -e 'A::B.foo'
invoke() not implemented in class 'Undef'
in Main (file <unknown>, line <unknown>)
So there's your error message. Linking it to the actual cause is something which you'll learn by experience.
So in that case, I don't get the name of the class which was mistyped in my program?
Correct.
And I don't get the line number of my typo?
Indeed not.
Or the file?
Right. You'll get no information about the location of the typo.
Is that intentional?
Well, no. As you see from the error message above, the information is meant to be printed, but it comes out as (file <unknown>, line <unknown>) instead.
Why?
Rakudo is built on top of Parrot. Usually, Rakudo generates its own error messages, but in some cases, Parrot will also generate an error. The error invoke() not implemented in class 'Undef' is such a case. When a Parrot-internal error like this one occurs, Rakudo will not be able to extract the annotation information required to provide a sensible line number and file.
I... I see.
Yeah. Sorry about that.
Are you able to pick up the irony in the fact that when I use namespaces to help mitigate the complexity of my project, I end up with an error message that in fact makes it harder for me to manage the complexity of my project?
Hold on.
Yes. We are able to pick up the irony in that. Quite easily, in fact.
Consider not using namespaces at the present juncture. They are very useful, but they are also known as a frequent source of annoyances like this.
By the way, I couldn't help but note that the line number and file information in your first example doesn't make any sense either. What the heck is src/gen_setting.pm and line 324?
Well, uh, that's the last line of internal Rakudo code that actually has a working line-and-file annotation. It's nothing that should reach the user, really.
So that's kinda broken, too?
Annotations are currently broken, yes. Apologies.
Back to my mistyped type name. My program is distributed over fifteen modules and ten thousand lines of code. How do you propose I find my typo?
First off, we recommend that you compile often. That way, the diff from the last working code will not be too large, and you will not have to visually scan so much text hunting for your typo.
Secondly, it's often useful to have your project in a version tracker such as Git, so that you can do git diff to see the changes against the index, or against the latest commit.
Thirdly, when all else fails, you can always insert print statements into your code, to try to bisect the origin of the error.
So in other words, Rakudo is no help whatsoever when this occurs?
Now, that's not quite fair. Rakudo tells you that the error occurs. That's actually useful information.
And you consider that adequate?
No, I didn't say that! No-one is happy about this situation. It's just the way things are.
So it can't be fixed?
Theoretically, yes. But not easily. Remember that the error occurs in Parrot.
Don't Rakudo and Parrot developers confer with each other?
Oh, sure we do. Do not assume that we're deliberately causing this situation. It's just that the current way Rakudo and Parrot are welded together makes the situation non-trivial to rectify.
So this problem is going to go away with the advent of the new ng branch?
There's nothing to indicate that this would be the case. In ng, you currently get a Null PMC access:
$ ./perl6 -e 'A::B.foo' # ng branch
Null PMC access in invoke()
current instr.: '_block14' pc 29 (EVAL_1:0)
called from Sub '!UNIT_START' pc 984 (src/glue/run.pir:17)
called from Sub 'perl6;PCT;HLLCompiler;eval' pc -1 ((unknown file):-1)
called from Sub 'perl6;PCT;HLLCompiler;command_line' pc 1489 (src/PCT/HLLCompiler.pir:794)
called from Sub 'perl6;Perl6;Compiler;main' pc -1 ((unknown file):-1)
To its credit, Rakudo ng does provide more information in this case, but unfortunately the information is of a kind which was concealed from the user in Rakudo master about a year ago (because it tended to be very uninformative).
Just to summarize: this all sucks, right?
That would be a succinct description of the state of this particular error message, yes.
I heard that the Perl 6 community has adopted very high standards with respect to error messages. There's talk about "awesome error messages", and last summer I was in the audience when Larry Wall demonstrated how good Perl 6 was at reporting error messages to the user. How does this error message square with all of that?
The awesome error messages are like a platonic ideal towards which all implementations aspire. Rakudo, being rooted in our imperfect physical world, doesn't always get all the way. Yet.
I'm about to go visually scan ten thousand lines of code, looking for where my error message might have originated. Any last words?
We value your efforts as an early adopter of Rakudo. Your feedback is important to us. Have a nice day.
Today plenty happened in Rakudo land - in fact, it was my most active day's Rakudo hacking in quite a while. colomon++ also made some great commits, and between us a lot of things moved forward today. For my part, hashes and pairs are in much better shape.
I wrote before that I'd got some Rakudo days left to write up; there are two of them, but I'll cover them both in this post, since some of the work crossed the two of them anyway. Here's what I got up to between them.
Thanks to Vienna.pm for sponsoring me to hack on Rakudo, not only for these two days, but also throughout 2009!
Several days before Christmas, encouraged by my mum asking, "when you're going to start your Christmas break", I stopped working and hacking on stuff and started relaxing. Until then, I hadn't realized just how tired I was. I slept quite a few ten hour nights in the following week, and had an enjoyable Christmas break. I'd figured I'd maybe take a week or so's break, and then get straight back to things, but a week on I had no motivation or energy to dig in again whatsoever. So, I decided my break would go on through New Years. New Year's celebrations this year involved curry - something I certainly wouldn't mind it involving again.
Early January brought several days in Sweden, part of planning for an upcoming refactoring of my work/location - there's details on my personal blog, but the short version is that I've accepted a job at a Swedish startup and will be moving there in March. It's not full time, so I'll continue to have time for Perl 6 development. They know about and, happily, are supportive of my involvement in Perl 6 and my continued attendance of Perl conferences.
I spent a weekend in Prague on the way home. I did it by train rather than flying, which was enjoyable. It snowed almost my entire time in Prague, and I caught a cold in the following week, but it was kinda worth it to wander around this beautiful city. Didn't bother studying Czech at all, and sorta got by with speaking Slovak, though some folks heard me speak and immediately concluded English would be easier. :-) Somehow it kinda felt like I was back somewhere I belonged, even though I'd never been there before. I love central Europe, and excited as I am about Sweden, I know I'll miss this part of the world a lot.
Anyway, I eased back into some work in January, but mostly took it quite easy. The happy result is that, come February, I'm finding myself recharged and ready to dig back into things again. I got some nice commits done to Rakudo yesterday, and today I meant to, but instead participated on an interesting thread on p6l and did some other useful meta stuff (like this post). Tomorrow should have plenty of hacking time though, and I'm looking forward to it. I also have a couple of blog posts to do about Vienna.pm-funded Rakudo Days I did in December, but never got around to writing up; thankfully I did make notes on what I did on them. :-) My main focuses from here on will be on:
Anyway, that's what's been up with me. If you take away anything, it's that you may not realize how much you need a break from something until you take it, and if it's not the only thing putting food on the table, then it's probably better to take the needed amount of break and come back revitalized. I guess the other option is to dig back in regardless, but I suspect that's the path to burnout, something I'm quite keen to avoid.
More technical blabbering here soon. :-)
SF took my challenge to heart and started producing a "modern Perl 6" version of the example code in E02. His thought process can be seen here, and here.
After being a bystander for a few hours, I coulndn't restrain myself anymore: I produced my own version. I should say at once that it's quite different from SF's: while he keeps close to the original E02 (which, in turn, sets out to prove that Perl 6 is/was not very different from Perl 5), my version is a bit more liberal in its interpretation. I do mix in some of my personal preferences into it. Some examples:
$ARGS prompts("Search? ") anymore, but there's a nice &prompt function which I used instead, together with a while loop.&show function now uses gather/take, rather than printing directly.&show code.&show makes a point of using a slurpy @_ rather than naming the paramters. I don't. (Neither does SF.)given/when construct in the &insert function. To its credit, E02 tantalizingly hints of it, but then does a MIB mind-wipe. (You don't recall that bit? Oh well...)undef to initiate the child nodes to some empty value. Both SF and I independently realized that just any undefined value won't work if &insert is to have %tree in the signature, because %tree only binds to an Associative value. SF solved it by putting Hash (an undefined Hash type object) in the child nodes, and changed it to Hash.new in the later version. I used {}, which should be equivalent to Hash.new, but IMHO more idiomatic.I believe rewriting the exigeses in modern form is a very worthy activity. I hope we'll see more of that. Perl 6 suffers a bit from stale, outdated documentation, and having these in new versions would be valuable.
It's also a very interesting historical activity to read the old apocalypses and exigeses, as I increasingly find. Perl 6 has come a long, long way since 2001.
The Perl 6 design team met by phone on 27 January 2010. Larry, Allison, Patrick, and chromatic attended.
Larry:
($a, $b, @a) = 1..* worksKeyWeight deletion criterion kept consistent with other KeyHash typesList, Seq, Parcel, Capture, Iterator, Nil etc.List is now simply the iterator role, and doesn't do Positional Seq takes over Positional duties for reified (or reifiable) value listsSeq now as a constant Array (but also lazy like Array)Iterable now means you can ask for an iterator, but doesn't do List Array, Seq, etc do Iterable, but not List List Nil is defined as a suitable sentinel for both list and slice iteratorsEMPTY special exception object to be the iterator sentinalE operator to go with it to make testing for EMPTY across multiple iterators very fastCursor's fnum->fate translations for shorter LTM candidates in preparation for smarter LTMPatrick:
List, Parcel, Itertor, and arraymap iterator that workszip operator was nice.iterator() method.list() returns a flat Parcel for that iteratorParcels know how to generate IteratorsIterators of IteratorsParcels to understand thatAllison:
c:
Allison:
c:
Patrick:
Allison:
Patrick:
Not all of Perl 6 is Rakudo. Well, when I use Perl 6, it is. But I'm hoping 2010 will change that. We have a few other implementations out there, which fall in the "small but promising" category.
Mildew
From the README:
I can't say I've ever understood the idea behind naming projects after icky things, but I like the projects as such. They seem to generate a kind of "basic research" which strengthens the foundations of Perl 6. The decade-old project has always been about attacking the enormously big task of implementing Perl 6 from different angles — and I like the angle pmurias++ and ruoso++ are taking.
Mildew targets both SMOP (written mostly in C) and the Google V8 JavaScript compiler.
Sprixel
From the README:
I know diakopter++ is playing around with both JavaScript and C♯ implementations of Perl 6 rule engines. That is also an area which excites me, and where I'm glad people are making headway.
Vill
From the README:
Heh.
Whoa! That's potentially very attractive.
I'm actually hoping I might be able to help with the C-based parser.
Conclusion
Rakudo is the implementation that shows up on the radar of most outsiders right now. (And with good reason.) But much exciting work is going on in the background, with implementations like mildew, sprixel and vill.
I'm sure this will come off as almost self-evident, but I'll say it anyway: the moment an alternative implementation will cross a threshold into the area of the really interesting, is when it provides a significant chunk of Rakudo's functionality (which is saying quite a lot), together with some feature that Rakudo doesn't have. Given Rakudo/Parrot's current performance, the most obvious feature would be speed. But it might also be something else, such as a bridge to Perl 5, or a very solid metamodel. The more ground-shaking the new feature, the less important will be the delta between the alternative implementation and Rakudo.
At the very least, I think one of the above implementations will pass through the Mach 1 barrier in 2010, and attract a serious user base, and more developers. I'd love for Rakudo (and Pugs) to have some company up there among the "big" implementations.
Exciting times!
The Perl 6 design team meet by phone on 20 January 2010. Allison, Patrick, Will, and chromatic attended.
Allison:
c:
Allison:
Patrick:
c:
Patrick:
Will:
Patrick:
Will:
c:
The Perl 6 design team met by phone on 13 January 2010. Larry, Patrick, and chromatic attended.
Larry:
:by modifierKeyHash docs to make the semantics clearer:!foo(0) and :5bar[42] that supply unexpected argsPatrick:
c:
:method should not add entries to NameSpaceThe Perl 6 design team met by phone on 06 January 2010. Larry, Allison, Patrick, Will, and chromatic attended.
Larry:
p{} to qp{} to avoid using up another common single lettersay/print is now just a warningbreak/continue to succeed/proceed succeed returns the value of the whole when blocktrue to so to avoid confusion of the predicate with the True enum valueAny up into Any and Cool typesCool stands for Convenient OO Loopbacks, or any other acronym you'd likeCool are the ones that do Perlish dwimmy coercionsAny by default, so aren't born with gazillions of methodsCool package:p (aka :path) so we can form the qp{} path literalsay/print say/print warning to anything a p5er might try that might be in p6 without the defaultingso, succeed, and proceed /\b/ and advises to use an appropriate p6 word boundary assertion insteadPatrick:
Larry:
Will:
Patrick:
Allison:
c:
Patrick:
I don't know what kept me away from generating code for so long. Fear and prejudice, perhaps.
I've been trying it the last few days, and I have two things to say. First, it's like learning to program all over again. Remember that sense of power from the early days, when just picking up coding? "Hey, I can program this piece of code to do whatever I want." Well, guess what? That feeling comes back when one starts down on the path to madn... erm, to code generation. Only it's stronger. "Hey, I can program this piece of code to program that piece of code to do whatever it wants!" I think I've just discovered meta-hubris. Most likely, I'm not the first to do so.
Second, there's a flip-side to the feeling of power. That other feeling is how you feel when you knit your brows and wish that your neurons would line up a bit better so you could think more clearly about the problem at hand. Who would have thought, that feeling is also stronger when you're suddenly writing two different, entwined and related programs at the same time, in the same file. In my case, the knitted brows turn into an empty stare and a jaw left slackly agape, as I sit there wishing that I was better at context-switching between runloops.
Honestly, I think I expected eval to be the source of much programmer confusion, but I have to confess that it seems I underestimated the vistas it opens up when you buy into the idea of generating exactly the piece of code you need for the task (from an AST, say), and then eval it into a closure. That's what the back end of a compiler ends up doing, so maybe I shouldn't be so surprised that it's a versatile technique.
Lately, I've been in the business of squeezing every drop of juice out of the already implemented control flow constructs already implemented in Rakudo. I'm writing a p6regex→p6 compiler, you see. (Yes, that's a rather crazy notion; thanks for asking.) Along the way, I've often felt the need for not-yet-implemented control flow. This has led me to this hope-inducing maxim:
Every type of control flow in programming languages is just convenient sugar for if statements and while loops.
ifs and whiles are the stone soup to which all the rest of our control flow can be added as seasoning. ifs let you conditionally skip ahead in code, and whiles allow you to conditionally skip back. That's all you need.
Here are some examples.
if/elsif/else statements. Even Perl 6's given/when constructs.next, last and redo, either with or without a label to affect a less-than-innermost loop, can be desugared to sad boolean-ish variables, plus some if statements to appropriately regulate the expression of the code inside the loop. (Yes, go ahead and twitch just thinking of it. That sugar is there for a reason.)Aside from the switch statements and unlabeled next etc, which already work very well in Rakudo, I've been doing the whole list of desugarings in GGE (the regex compiler). The part with the continuations was especially fun. I needed them for backtracking, at least as long as the compiler was only an interpreter.
But then, during a fruitful discussion with diakopter++, I was told how to emulate (delimited) gotos with a switch and a loop. The idea is quite obvious in retrospect: just keep the current 'label' in a variable, and switch on it in each iteration. Presto! I should have thought of that. I don't even need to flee to PIR any more.
I took the idea and generalized it to delimited gosubs: instead of keeping the current label in a scalar, keep it at the top of a stack. Define macro-like constructs to push to (local-branch) and pop from (local-return) the stack. Suddenly I don't need continuations as much.
Result: this. We send in the regex /<[a..b]> | <[b..e]>/ on the top line, along with the target string c to match on. The program generates an AST, an anonymous subroutine which executes the regex in atomic Perl 6 operations, and finally a match object which indeed finds c to be a match.
Here's a similar but slightly more involved example. And here's one doing captures and backreferences inside a quantified non-capturing group. Isn't that exquisite? (Ok, bad choice of word. Sorry.)
As I said, I wrote most of with a feeling of being not just in over my head, but of being in over my head twice. I'm still a bit surprised it works. The runtime compilation seems to introduce a bit of a speed penalty, but (1) it's a one-time cost, since you can re-use the regex object, and (2) I told you it would be slow.
The code-generating work still resides only in a local branch on my computer. I'll push it to master as soon as I'm done bringing GGE back to its former capabilities. (Update 2010-01-24: Done, and done.)
Code writing code. What a concept!
The Perl 6 design team met by phone on 16 December 2009. Allison, Patrick, Jerry, and chromatic attended.
Patrick:
use and import Allison:
Patrick:
.DEBUG rulepanic statementsAllison:
Patrick:
Allison:
Jerry:
Allison:
c:
The Perl 6 design team met by phone on 09 December 2009. Larry, Allison, Patrick, Will, and chromatic attended.
Will:
Allison:
Patrick:
Larry:
PairValSet, renamed it EnumMap PairSet is now PairMap, and PairVal is just Enum .enums on hashes and arrays as well as enumerations.pairs, which give reference semantics into the values of the original data structure.enums gives you a constant snapshotEnum.name to Enum.key, and he was right, since they're constant pairsenum enum is compile-time evaluated as an anonymous list of constantsEnumMap at run time for the other behaviorWHENCE closure as part of the typename, rather than relying on subscript parsec:
Patrick:
push_eh an ExceptionHandler onto an array in a context, it creates an RPAAllison:
pushmark/popmark stuff to do actions used that same global arrayPatrick:
pushaction and popaction before they go awayLEAVE semantics, we want to avoid generating an exception to leave that scope for cachingAllison:
FAIL and RETURN local_branch and local_return uses that arrayPatrick:
bsr and ret may haveAllison:
Patrick:
local_branch or local_return The Beyond and below are like a deep of ocean, and we the creatures that swim in the abyss. We're so far down that the beings on the surface superior though they are can't effectively reach us. Oh, they fish, and they sometimes blight the upper levels with points we don't even understand. But the abyss remains a relatively safe place.
Vernor Vinge, A Fire Upon the Deep
On behalf of the Parrot team, I'm proud to announce Parrot 2.0.0 "Inevitable." Parrot is a virtual machine aimed at running all dynamic languages.
Parrot 2.0.0 is available on Parrot's FTP site, or follow the download instructions. For those who would like to develop on Parrot, or help develop Parrot itself, we recommend using Subversion on our source code repository to get the latest and best Parrot code.
Parrot 2.0.0 News:
Features
Platforms
Performance
New deprecations
Tests
Tools
Miscellaneous
Thanks to all our contributors for making this possible, and our sponsors for supporting this project. Our next release is 16 February 2010.
Enjoy!
The other day, I remembered this old piece of #perl6 backlog from 2005:
<masak> question: what are good ways in p5 and p6 respectively, to reverse a string?
<masak> the easiest way i found in p5 was join '', reverse split // $string
<masak> doesn't look very nice, now does it?
<integral> *blink*
<integral> $string = reverse $string
It feels odd to realize this five years later, but it seems that in 2005 I didn't have a firm grip on how reverse worked in Perl 5. Chances are, dearest reader, that you do. But if not, the rest of the refreshingly frank discussion will explain it.
Meanwhile, five years earlier, I persist in my innocent ignorance:
<masak> nope
<masak> doesn't work :(
<masak> reverse only reverses lists... i think
Reading this from the perspective of five years' work with Perl 5 and 6 is... enlightening, in a slightly cathartic way. Sure, it could have been that I'm the first to discover that reverse in Perl 5 doesn't in fact reverse strings, despite thousands of people using it daily for that purpose. But the chances of that are astronomically small. My peers on the channel tell me this.
<PerlJam> masak: clearly you are insane.
<integral> perl -le '$string = "abc"; $string = reverse $string; print $string'
<integral> masak: the manual *clearly* explains all the stuff about context
<integral> and the faq
They do, you know.
<PerlJam> masak: in perl6 it would be $string.=reverse probably.
This was true in 2005, but nowadays we have flip for strings, reverse for lists, and invert for hashes. The need for different functions falls out naturally from the fact that Perl 6 doesn't depend as heavily on context as Perl 5 does.
Back in the log, I'm still trying to reintegrate into reality.
<masak> integral: your example worked, thx
<masak> but nothing worked for me
<masak> apparently i am insane :P
PerlJam and integral are one step ahead of me.
<integral> no, you don't understand scalar context. perl -le 'print scalar reverse shift' foobar
<PerlJam> masak: you were probably saying "print reverse $string"
<masak> no, but maybe something of the sort
<masak> and that doesn't work, because...?
<integral> masak: print's prototype is (@), ie list context. It's a rightwards named list operator
<PerlJam> masak: context.
* masak thinks he sees it now
These explanations are actually very good, but just in case, let me restate them in my own words: reverse has two main behaviours. Either it reverses a list of things, or it reverses a string of characters. It switches between these two behaviours based on something. You might think that this something is what type of thing you send in (a scalar or a list), but that isn't so. Instead, reverse responds to its surroundings and figure out what they expect. $string = reverse $string is a scalar assignment, and expects a scalar. print reverse $string, as integral explains, puts reverse in list context, so it reverses the list of one thing ($string), i.e. doing nothing.
Steve Yegge has this to say, in a vitriolic critique of Perl:
Perl also has "contexts", which means that you can't trust a single line of Perl code that you ever read.
I would say that it's actually not that bad, and the idea of context can be unintuitive at times, in many cases it's actually very natural and useful. reverse, in my humble opinion, is not one of those cases. I'm glad it's split up into different methods in Perl 6.
At the end, we learn that I had actually Read The Faithful Manual already, I just hadn't read it carefully:
<PerlJam> masak: perldoc -f reverse
<masak> thx, integral and PerlJam
<masak> PerlJam: I read the perldoc entry but apparently not carefully enough
* masak reads it again
<masak> ah
<masak> "In scalar context, concatenates the elements of LIST and returns a string value with all characters in the opposite order."
<masak> this somehow went past me as something i didn't want :/
In summary, I mostly wrote this blog post because I like to make myself squirm. 哈哈
But I guess there's also a moral to it all. We all start somewhere, and in a way it's reassuring to find five-year old proof of this fact. A newbie is just on a part of the learning curve you've already visited; they haven't had a chance to tweak their keyboard and developing environment to maximum efficiency yet, and they sometimes forget that the manual is there, or misread it in some way. So, don't hesitate to be be kind to them, and help them connect to the goodness that is perldoc, PerlMonks and Planet Iron Man so that they can grow and bloom into experienced wielders of Perl.
But don't hesitate to call them insane, either, when the situation calls for it.
A class hierarchy of expression nodes: it's so much the prototypical use case for run-time method polymorphism that it's almost a cliché. One can close one's eyes and picture the way parts of the expression tree interact in rich, complex ways, shaped by the very types of the nodes themselves, in a dynamic dance of late bindings and virtual methods. Switch statmement, get thee behind me. Et cetera.
I'm building one. And I'm having almost too much fun doing it. In between trying to use the strengths of Perl 6 and keeping true to the original program I'm porting, I've discovered an important thing: Ovid is right about roles.
Specifically, I'm having trouble picturing how I would cram all the type information into my expression node class hierarchy, were I not using roles. The roles definitely help manage complexity in my case.
Here's a pretty diagram of my class hierarchy.
It's a flat beast. Apart from everything deriving from Exp, I have only one case of old-skool inheritance in the diagram. And even that one is more making a point than actually shortening the code.
Then there's all the colorful dots, representing the roles I'm mixing into my types. Some are for convenience (like the blue ones), others are vital for my program (like the green ones), and the rest are somewhere in between on the convenient/vital scale.
I even have a case of inheritance between two of the roles! Which means, in practice, that those classes with an orange dot also act as if they had a red dot. Very handy.
During the infancy of Rakudo, I've gotten used to learning to live without various features. Were I to do what I'm doing here without using roles, I could use two other mechanisms. The first is regular inheritance. The very thought gives me a bit of vertigo; I don't think I'd be able to turn the colored dots into base classes. Definitely not all of them at once; I'd have to choose. And that choice would affect the entire design of the program, probably resulting in loss of clarity.
The second way I could compensate for not having roles would be by using .can a lot. The presence of a given role in a class is isomorphic to the presence of a given method in a class. So that would definitely work, but I don't think I would like it as much. There's something to be said for declaring is and does relationships at the very top of the class declaration.
All in all, I'm very happy about the way things work. I'm wondering whether, had I not read all of Ovid's posts on managing the complexity of class hierarchies with roles, I would have come up with this design myself. Maybe, maybe not. But anyway: thanks, Ovid! This rocks!
A still-open question for me is whether the topmost type, Exp, should be a class or a role. Synopsis 12 has this to say about when to use roles:
Classes are primarily for instance management, not code reuse. Consider using C when you simply want to factor out common code.
I am using Exp for code reuse, and for giving all of the other classes in the hierarchy a common base type. So I guess I could indeed turn it into a role. But it's just that... I don't see a reason to do so, and I still feel instinctively reluctant about it. Maybe I'm a bit hung up about it being a class hierarchy.
This point has come up before on IRC, and I've yet to hear a satisfactory way to resolve it: when faced with making a base type a class or a role, which way should one go?