Lessons from implementing Hurl

Friday, December 15, 2023

I'm proud to announce that Hurl is officially released and done! You can check out the docs on hurl.wtf.

The language itself came out of an interesting question: Python sometimes uses exceptions for control flow, so could we implement a language that eschews normal control flow and only uses exceptions? The answer is yes, and it produces a language that's less bad to use than I expected1!

In the process of implementing it, I learned a lot. Next year I'm going to try to make another language to learn about type systems, and that one should be more normal (but no promises). Here are a few of my main takeaways from building Hurl.

Working without control flow is... fine?

I thought it would be totally mind bending to work without ordinary control flow. The first couple of programs were mind bending but then you just learn the common patterns. If you need to do an if-else, that's a catch (true) ... catch (false). Looping is harder to wrap your head around but it's also not so bad.

And the thing is, this is a general purpose programming language, so we can build this control flow. I ended up with a function called if that takes a condition function and a body function as variables, and it runs those. So you can write code like:

if(func() {
  hurl year == 2023;
}, func() {
  println("Hurl was written in 2023!");
});

It's not as clean as an if in reasonable languages, but it's also cleaner than I expected. Part of this is also because I used dynamic scope, not lexical scope, so functions can operate more easily over outer scopes, but it would be doable either way with minor changes.

This has me really excited to explore things like assembly. I've been really intimidated by it my entire career, feeling inadequate and all the usual impostor feelings. But now I can see concretely that eschewing normal control flow won't be a problem in itself2. I've had The RISC-V Reader on my desk for a while and now it seems more approachable.

An unexpected lesson for me, but I'll take it. So expect to see some RISC-V content next year!

All the nice things are so hard

My ambitions for Hurl were larger than my skills and time allowed for, and I had to pare it back. The things that got cut were any of the tools that would make the language fairly nice to use, and error messages unfortunately went by the wayside.

I realized this would be harder than expected when I started writing the formatter and then I started to rethink some of the other ambitions I had.

In a future language I will come back to some of these things. I'd love to write a slightly more sophisticated formatter that makes things a little prettier (though having one at all is an accomplishment I'm proud of). And I am really interested in exploring writing a language server for a homemade language next year, but this year I just could not work on it.

The big thing I have a lot of appreciation for now is the quality of error messages and debugging support in other languages. Generating error messages that point to where the error happened in the executing program requires that you track all the line information at run time! And that means you have to design it in from the beginning. Guess who didn't realize that and made some mistakes that would've been trouble to fix later?

Yeah, all those things that make a language nice to use are just a lot of work. And my promise (to myself, to you) is that my next language will work at these and the goal will to be fairly pleasant to use as far as educational language projects go.

Writing your own parser/tokenizer can makes sense

After working through Crafting Interpreters in 20223 where we implemented the tokenizer and parser from hand, I left with the impression that this was an intentional choice for instruction but perhaps not how we'd do it for real. Since then I've learned that a great many languages do roll their own tokenizers and parsers, and I still wasn't sure why.

I used pest to generate my parser for Hurl, and in a lot of ways it was pleasant to use. But on the other hand, it felt fairly restrictive and it was a lot to learn. In the end, I'm not sure that I saved time over writing my own parser.

If you write your own tokenizer and parser, you get full control and you avoid adding another dependency. They're also not that difficult to write (but there are a lot of details to get right, so they're tricky to get fully correct).

I would probably use pest again for a real project4 and it's used by some quite respectable projects like mdbook. But for for my next language, I'm going to write my own tokenizer and parser again. It's pretty fun, I don't think it'll cost me much time, and yeah why not?

Relying on the OS stack was a big mistake

I implemented a tree-walk interpreter, and recursion5 is the only way you get looping, which results in a problem: as you loop, you push onto the stack and you get stack overflows. And it's the OS provided stack for the interpreter which you end up blowing, so this isn't something we can just patch in Hurl itself. This isn't something I thought through critically before deciding to do a tree-walk interpreter. I knew that these are limited in some ways, but forgot how it would impact Hurl.

One solution here would be to migrate to a bytecode interpreter. That's a big project and would be a rewrite of the whole interpreter, so it's not in the cards. There might also be a way to optimize out some of the recursion here and create a loop from something tail recursive, but I don't know.

Another solution would be to add a new language construct. I'm not sure which language construct would help us out here, so it's an undetermined thing at the moment.

In the future, I'll make sure to account for this from the beginning and use a bytecode interpreter approach, or transpile to another language.

Licenses can be fun

Software licenses don't have a reputation for being particularly, uh, exciting6. But they don't have to be boring! They can be an opportunity for play, too.

Part of creating Hurl is art, and the license choice is a big part of that. The best license choice would have been an OSS license and then later do a rug pull and relicense as BSL, but that would imply that this project would get any attention. The second best license choice was to lean into my values intentionally. I ultimately decided to pick a license that would:

  • permit funny outcomes
  • allow educational use
  • reflect my morality and ethics

And to do this I settled on not just a license. Not dual licenses. No, that would make sense.

Hurl is triple licensed.

You can choose which of the licenses applies. You've got the standard AGPL-3.0 (no "or later" here, I don't want to be bound to the FSF). You've also got the choice to buy a commercial license (serious inquiries only 😉). Or you can use it under GAL-1.0 (the Gay Agenda License 1.0). Here's that license, in its full glory7:

# Gay Agenda License - 1.0

Copyright (c) 2023 Nicole Tietz-Sokolskaya <me@ntietz.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

  - The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
  - The grantee shall actively support rights for all LGBTQ+ people, respecting
    their gender identities.
  - The grantee shall say "be gay, do crime" at least once during use of the
    software.

The license is immediately revoked if the grantee supports restricting the
rights of LGBTQ+ people.

If the grantee is found to not have said "be gay, do crime" during use of the
software, the grantee has thirty (30) days to remediate this violation without
loss of the license. If it is not remediated, then the grantee's grants via
this license are premanently retracted.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Feel free to use this software for your own projects, with a few caveats. It was modified from the MIT license, so most of it is boilerplate, with a couple of additions to it. The main thing to note if you do for some reason decide to use this license is that it is not tested and I would probably be surprised if it's enforceable. No lawyer has been involved or harmed in its creation.

At the end of the day, though, what license is enforceable if you don't have the money to fight Amazon on it?

Playing is very educational

This was the biggest takeaway. It's not new to me, and I've written before that you should write more "useless" software. It was a great reminder of the joy and learning that can come from a long project that's just for fun and that has no practical value.

Along the way, I had a lot of fun. I'm not sure if I made friends or enemies. And I sure did learn a lot. Some of what I learned, I can apply at work starting this week8! And some of it is just added context for why certain things are hard, and makes me more deeply appreciate the tools that our dear language teams give us ❤️.

Go forth and write some playful code!


1

It is still pretty bad to use, though.

2

In fact, assembly might get us a little closer to ordinary control flow than Hurl does.

3

Highly recommend this magnificent tome if you want to learn from a professional language person. And his illustrations are beautiful!

4

I do have something brewing at work that will possibly use it, or nom.

5

Despite the relation of names, the Recurse Center has no fault in the creation of Hurl. The people there are quite lovely, and most don't implement languages like Hurl!

6

To all my lawyer friends out there who are reading this and vehemently disagree, reach out to me, would love to chat.

7

This license was inspired by boringcactus's post An Anti-License Manifesto.

8

Jessica, don't worry, I'm not going to actually use Hurl at work.


If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts and support my work, subscribe to the newsletter. There is also an RSS feed.

Want to become a better programmer? Join the Recurse Center!