Normally, I subscribe to the "YNGNI" philosophy: You're Not Gonna Need It. The idea is don't over engineer things because you'll likely spend a lot of time on things that never get used. This post is about an exception to that rule that I wish I'd followed.
When I started designing KRL, there wasn't much in the way of variable scoping and it didn't really need it. Each rule was a local scope and there were no global variables. Only one ruleset ever operated at a time. Consequently, I took the easy route and instead of designing a proper environments module that managed symbol tables through extension and recursive lookup, I just used a hash.
I find myself with a language whose requirements have grown to include global declarations and multiple rulesets firing at the same time. Ad hoc namespacing in the hash that manages the rule environment was getting out of control. The final straw was the need to add global variable bindings to the language so that, for example, you could make a single query against a data source and then share the results in any of a number of rules that might fire. I wasn't about to pile that on top and hope it didn't fall down.
Consequently, I've spent the last two days refactoring the language to use a proper variable environment. A few observations:
- The code is much cleaner now. Not only do I feel better about that, but I'm more likely to see how things work and not make mistakes in future revs.
- I'm extremely glad to have a test suite that I'd already written that helps me feel more confident that I haven't changed the semantics of the language with this change.
- Any change this big not only changes the code, but also the test suite (mostly calls that change and return results that change). So, that reduces my confidence in consistent semantics somewhat.
The main point of this post is simple: when it comes to building intepreters for a language, no matter how simple it looks when you start out, take the time to build and use a hierarchical environment system for managing variable scoping. I wish I had. I'd have saved myself time along the way and not needed to spend two days refactoring the code now. You're gonna need it...