Become a Creator today!Start creating today - Share your story with the world!
Start for free
00:00:00
00:00:01
Building A Programming Language From Its Core (with Peter Saxton) image

Building A Programming Language From Its Core (with Peter Saxton)

Developer Voices
Avatar
4.4k Plays4 months ago

A language’s AST—it’s abstract syntax tree—is nearly always a hidden implementation detail. It’s not treated as part of the language, but merely the intermediate step between parsing and compiling. But this week’s guest aims to flip that relationship on its head...

Peter Saxton joins me to talk about EYG - an AST-first language that defines the fundamental capabilities first, and then stretches out from there to surface syntax and final execution.

The result is something that can teach us a lot about how a typed, functional programming language works; how an extensible effects system works; and could make writing a new programming language as easy as defining the syntax you want, and parsing that into EYG's AST.

--

EYG Homepage: https://github.com/crowdhailer/eyg-lang

TinyGo: https://tinygo.org/

Become a Supporter on Patreon: https://patreon.com/DeveloperVoices

Become a Supporter on YouTube: https://www.youtube.com/@developervoices/join

Kris on Mastodon: http://mastodon.social/@krisajenkins

Kris on LinkedIn: https://www.linkedin.com/in/krisjenkins/

Kris on Twitter: https://twitter.com/krisajenkins

Recommended
Transcript

Defining a Programming Language

00:00:00
Speaker
What makes a programming language what it is? What's the essence of a programming language? You could say it's the syntax, because at first glance, we look at some code and we say, ah, that's Ruby. That's C. But we don't really mean syntax. That's a bit of a surface analysis. More realistically, a language is the set of features it offers you, maybe in tandem with its library ecosystem. Or to some people, it's the community or the job opportunities. There are a few different ways of looking at it.

Importance of AST in Programming Languages

00:00:30
Speaker
But I think my guest this week would argue that if you want to understand the core of a language, if you want to find out what it really is, you should look at its abstract syntax tree. The thing that's left after the parser has boiled away all the syntactic sugar. That's the core definition of what this language can do. And that's the thing we should focus on.
00:00:51
Speaker
Or at least that's the thread that Peter Saxton is pulling on with his language, EYG. It's a project that puts the AST absolutely at the front and center of what he's building. And I think in doing that, he's done a number of interesting things. He's creating a potential spec for what a modern functional programming language with typed effect handling can be. And he's creating a hands-on way of understanding how you build such a language and what you have to do to extend it. And he's providing an off the shelf engine for anyone who wants to create their own language. You just write the parser, plug in EYG, and you're probably already months ahead of schedule, which I think makes it very interesting for domain specific languages.

Introducing EYG: A New Language Engine

00:01:40
Speaker
There's really a lot to learn here and a lot to unpack from an AST.
00:01:44
Speaker
something that at its core is as simple as one rich data type. So let's start unpacking. I'm your host, Chris Jenkins. This is Developer Voices, and today's voice is Peter Saxton.
00:02:09
Speaker
Peter Saxton, welcome to the show. How are you? I'm good, thanks. Thanks for having me. It's an absolute pleasure. you've um You've got an interesting project that I feel I can learn a lot from, ah yeah especially about how a modern functional programming language is designed. But before we get into what I want to get out of this, what are you trying to do with your language EYG?

EYG's Design Goals and Challenges

00:02:32
Speaker
What's your aim? So there's been quite a few aims over the time that I've been building it, but at first it was about ah separating a language from the machine. So it was basically getting rid of any ah detail about the specific computer that you're on from the language. So no assumptions about numbers or your IO or a file system. OK.
00:02:54
Speaker
this puts you um This puts you in the... I always think of this as a divide in computing, like is it a chipset building upwards or mathematics building downwards? And this puts you firmly on the mathematics end. Oh, 100%. Is that fair? Yeah, that's definitely the case. Okay, so if you if you're going to build an entirely abstracted away language as a starting point, what's what's your next set of design decisions from there? Well, I mean, the kind of obvious thing is how you then do deal with the outside world. So the joke with a purely functional language is it does nothing but warm up your computer. Um, and that doesn't obviously need to be true. You can have, you know, a single script can tell you what you should do next. Um, but yes, you then.
00:03:38
Speaker
The question is how do you make it sort of ergonomic to be useful? Um, so, so the name very much alluded to what I wanted to get out of this. So EYG is for eat your greens. And the idea is, you know, you're great yes, a bit of essentially restriction upfront should benefit you later. So, uh,

EYG vs. Existing Languages

00:03:54
Speaker
the kind of deal was if I made these things perfectly pure, the compiler or other the tooling should be able to give you more help than you were used to. Otherwise why go through this, this effort? So it's a kind of, yeah. is this Does this stand in reaction to some particular language you have in mind? and Essentially almost all of them. um think I can think of particular bugs um I dealt with. So the last thing I was doing professionally was writing Go. And we had an abstraction where we had tried to do the imperative.
00:04:26
Speaker
kind of the functional core and the imperative shell, so you know most of the business logic didn't touch the sockets. And I had to debug something where most of the code didn't touch the sockets, except in one place where it did. So deep, deep in the business logic, there was a piece of logic which just went, we forgot to get this from the web server. let's make a Let's make a sneaky extra call because we know we can access the network. And so, yeah you know, once you start putting guarantees around having a stronger type system, it came up quite often that that was like the next problem that I was dealing with. Like, you know, the type system made me more productive. Um, and I was productive up until IO started becoming an issue again. Right. Yeah. So it's, there's this.
00:05:08
Speaker
idea that we can move like side effecting stuff we can teach a compiler about side effects and then it can teach us things about our code base like you're sneaking in network access right down there in the stack yeah absolutely and you can just interrogate it incredibly quickly going Yeah. why why Why do I have a log message? Or there's a log message I don't like because someone's put debug. You can just, I mean, so in my editor, it'll, you know, you can hover over some code and it'll tell you the effect. So you just run your mouse down and you go, yeah, there's this cannot touch the standard out. So it can't be coming from here. and Yeah.
00:05:44
Speaker
This uses the only side effect of logging, so that's fine. Yes, exactly. Other of than that, it's pure. yeah yeah yeah But there are languages that do that already, right? You you could point at Haskell and say, job done. Why why go to the effort of writing a language? Well, i say so the language for a long time I tried not to write a language that other people might use. So I think that there's a good answer of like, why not another language? it's It's a fair question, but I think there's still quite a few spaces we haven't explored. um So I've been involved with Gleam for a lot recently and Gleam is a very good, it's ah a minimal subset of, kind of it's a useful amount of A functional language. Whereas Haskell that you bring up is definitely still a research bed. Um, and I don't think, so I think that Haskell probably does have an answer to this. You know, you can do a free monad and as many as extensions as you want, but there's no simplicity left. Like it's a great testing ground, but it's not like ergonomic. Whereas his g gleam is incredibly ergonomic. Um, it's an incredibly productive thing, but it hasn't addressed this yet. You know, it is not pure, you know, there's no IO monad you need to run through. Um, so there now becomes a question of like, well, which is the next.
00:06:54
Speaker
bit the Haskell's explored that you need to ergonomically wrap in the language. So for a long time, I kind of wanted to put effect types in something like Gleam or some other language, but I discovered that wasn't going to work. And so I've got to the point where no, like EYG has enough unique things that I don't think it particularly crosses over with any other language in everything it chooses to do. Okay, yeah, I'm i'm reminded of um Elm, which basically splits everything into no side effects or any side effects you like. Yes, which is, you know, it's a good really robust first starting point, um but I think we can do better.
00:07:29
Speaker
OK, so we've got to get into how you've implemented this, because from what I've seen, and tell me if I'm wrong, looking at the code that you've written, it feels like this is both a good place to start a language and a good place to start learning about how one might implement this kind of language.

Effect Handling in EYG

00:07:47
Speaker
Yes, so the um the kind of the technology that it's all built on is is effect types. so Like effect types are, for people who have been playing around, they're in languages like Koka, which was pretty much a research language to explore effect types. And then things like, you know, Unison and Rock have done like more practical things with them. So yeah, so there's two sides of implementing effects. So there's the the runtime and the type checking, and it's nice that you can realize that they are totally, totally separate things. um And in many ways, I think actually the, um
00:08:20
Speaker
and the kind of runtime side of things explains what they are better. um So when you're writing, so EYG is interpreted, although there are also options to transpire it to JavaScript. um So yeah, so when you're you know walking you through your naive tree walking interpreter, an effect is it just a situation where you essentially suspend that state. So there you have this Recursive loop, so you iterate through a step. So you have the CEK, so control environment and continuation ah interpreter. And then when you get to a an effect, essentially all the environment and the continuation are just made available to you and you you ah stop and you come to some handling code.
00:08:59
Speaker
so you know Because your interpreter for the language must be made in some other language. um So you got to yeah you get that happen when you essentially suspend execution and you say, outside world, please give me something to do. um But the reason I think this explains what they are quite well is you can resume them more than once. um So you really get to the point where you've separated what your program does. So when you get to this suspense point, you get, say, for example, please make a web request. And you get a continuation which says, ah when you have a web response, like an HTTP response, call this continuation with it. But because everything is pure in a data structure, you can call it more than once. You can say, what happens if the web request succeeds and what if it fails?
00:09:43
Speaker
um and start walking through. yeah um And by this point, you've entirely abstracted away what it means from your machine um to be talking to the outside world. Because if you didn't like how the outside world reacted, you could always just go, well, actually ah I got a 400 first time, but the program they just exited saying, I can't continue, but you could save that state and you could give it a 200 later. and be straight back into the program you were running on. So you could build in like retry logic into the handle and not your code. Yes, you could. read yeah And you can save it to disk as well. So you could retry it. You could turn the computer off and and retry it later.
00:10:21
Speaker
Right, so the intuition here is if I have the side effect of accesses the internet, my code is actually going to be turned into a recipe that will eventually be called when the internet was accessed. Yes. Is that fair? Yeah. Okay. Although it's not even ah explicitly when the internet was accessed, it's just whenever you decide to give it a response, because you would do exactly the same for test harnesses, you remove any concept of mocking in this structure as well. If you just immediately give a response, it's the same infrastructure of a response was found. So it could be found from ah a test case, you know, an explicit example test case, it could be found from a, what do you call it when you do the many test cases, you could make a random response and see if your thing will quick check that one property testing.
00:11:05
Speaker
yeah property test um or like a VCR recording of the last time you made it, like a cached response. um You just have to get a response from somewhere. OK. So we're in the world of um functional programming and union types. That makes me think you could have you could take one of these recipes, one of these effect codes, and know basically all the different kinds of input you could get and try and evaluate it with all of them. Yeah, depending on what kind of data type you've got. So ah there's an example I had where one of the effects was just choose. So random number is something that's not pure, so you need to make heaven effect. So choose was the equivalent of flipping a coin, so head or tail. And yes, enumerating everything that might happen is just two. You just say, well, what happens if the program runs with true and what happens if the program runs with false? Obviously, if your thing is to make a web request to pull a video stream, technically any combination of bits that was a valid video stream
00:11:58
Speaker
So you can't properly test all of them, but there are definitely effects where you can just go, um yeah, I'll enumerate every single possible thing that might happen. And you also get effects where if the effect is only a side effect and doesn't have like, so for example, logging, you write a log, but the return type of that effect is just unit. Like it's just. Always the same thing. and I did something and now I'm just telling you that I've done something. Yes. that So you don't actually need to go to the log file because you know, the program's going to return with unit. Like there's no other value that could be got. So you can crack on evaluating the next thing without, you know, depending on if you want the semantics to be, I will only continue the program if it's been written to the log file. But again, the program doesn't know because it's, so if you continue it, you know, if people call a log effect wanting the log to be in the logs, you can still run the next bit of the program.
00:12:48
Speaker
no one can see anything until it does another effect. So if it throws an effect before you've written the log, you just don't do anything with the second effect until you've actually got the login. So you can always kind of be working your head on the next bit of execution. ah right yeah yeah yeah yeah so I'm trying to think how this works under the hood because it's not obviously intuitive that when you compile a program you're sort of keeping the program around as a thing to be executed later. when handle yeah Tell me how it's evaluated underneath.
00:13:20
Speaker
So, I mean, there's a couple of ways to go about doing this. I mean, if you write an interpreter, then it's not really your continuation data structure, whatever he is in your interpreter that just needs to stay in in memory of the, um so the, ah the interpreter program, like the runner has to understand how to make these side effects. So you just have a ah global variable that just says my current Envin stack is these two things. Um, cause a simple interpreter doesn't need to have any concept of like parallelism. So there's no built-in. you know difficulties around that. So there's if you're only executing one thing at a time. So that's kind of the simplest way ah to do it, essentially. The other thing to do is these these continuations, um these stacks, these continuations can always be turned into kind of function code. So you then consider them ah closures where you had like, you um it's just a function that you're going to execute where the first argument is the return type from the effect. And then you need to capture the environment like any other closure.
00:14:16
Speaker
um So you can use the standard kind of Lambda lifting. So if you make all your closures, not need the environment, you know, the the thing to do when it returns is just call a function, which you have a pointer or a reference to with the return type and know that the ah kind of closure or Lambda lifting has taken care of everything else you need to already. Okay. Yeah, I can see that. That this does lead into then how we're structuring the AST, right? Because yeah Your interpreter is running all off a single AST, which is a core thing. Give me some intuition about how large that is and how that's structured. So the AST, so yeah, so EYG has a deliberately small AST in terms of the number of data types. um The AST, I'm trying to think how large you can get for a running program. I'm not sure I have much intuition that EYG does anything different, like better or worse than than other interpreted languages.
00:15:16
Speaker
um and Normally, your're your your program ah like the data structure of the program is not necessarily the largest thing because it's kind of created by hand. There's never a terabyte of AST or anything that you that you load up. um But i mean an interpreter is the most inefficient way to sort of implement a language, and there's nothing changed about this here. so it's you know that My implementation has currently been focused on simplicity. um so yes i You know, I would tree walk, you know, i I have an AST, which is a big data structure and I tree walk it. And if the continuation is very large, I do clever things to make sure that I do have tail call loops like recursion. So you you you don't build the stack up indefinitely. ah But beyond that, it it it grows with the size of your program and how deep in the call stack you are.
00:16:04
Speaker
OK. OK. So we should go through some of the parts of your EST. Because what do I need to, if I know how to make a calculator, which I can just about remember from university, yeah that level of syntax tree, what do I need to add to that to get to a typed functional programming language with side effect handling? So with the side effect handling, I mean, the only one that you need is you need a, so I call it perform in my EST. So you perform a side effect. so ah Perform is a AST node which has a label. So the the log side effect, you do perform with the the string key log and you know the fetch, you know with the string key fetch. um So in an interpreter, so I mentioned CEK when you know you're you're going through.
00:16:48
Speaker
You take your current node in the AST, which is the C, the control structure, and you have the current environment and your current continuation. So if you come across ah something like you know a literal, like a number, you just turn the AST node into a value. And you pass this through the continuation. you know like this is There's nothing changed here. And if you come across, say, an assignment node, an assignment node, you extend the environment because you're saying x equals something. so you extend the And then you loop and you carry on. If you come across this perform node, so when you come across the perform node, so this inner loop state is this control node, which is the next piece of the AST, the environment and the continuation. All you need to do is just exit the loop. you know When you come across a perform node, you just return at you know a tuple of
00:17:32
Speaker
i like I, as the evaluator, don't know what to do now because I've hit the outside world. So you just perform, yeah, you just return. You just return saying the current effect is log. um The argument, which will be on the um environment, or actually on the stack in the case, so ah is available to you. And the environment and the continuation, you just return all three. And so you return all three and you do whatever you do to meet read the outside world. And then when you've got what you want from the outside world, you just go back into that loop again. You just replace the perform node ah with um but the return value.
00:18:06
Speaker
So you're saying that the interpreter's job is to kind of be a purely functional interpreter until it hits these before nodes. Yes. and then And then you've got to add something into your you. Then you begin to extend your interpreter to handle those specific effects. ah Yes. Yeah. you and that yeah that's the That's the way I've done it. There's a few other ways to like push them inside, but that's the way I've done it. Does that mean you can't define have user-defined effects, or that they just have to be defined in terms of the language? They must be defined in terms of the host language host interpreter's larger capabilities. Yes. so Well, this is why you want to be able to sort of type check over them as well. But you can, yes, when you return, because I'm saying that it's ah it's a string key. um So any any there's nothing in my language that limits what effects you can have. um
00:18:55
Speaker
and yes like so We're still talking about like the value runtime. We're not talking about type checking. so it's It's possible that you might get something that's incorrect ah because you can run this interpreter without type checking or you can type check and see if it will error. um so yeah so If you get back an effect that you don't know ah that comes to the top level that you just abort, essentially I say that I got received a runtime error and the runtime error is like unhandled effect. um But what if what if like you give me your interpreter and it just defines the effect right to file? I know I can create, somehow I would be able to create an effect called logging. yeah that just But what's your mechanism? How does that actually work in the language that I define my own custom effect in terms of a supplied host effect? So one thing we haven't mentioned yet is effect handling. so um
00:19:45
Speaker
so So I mentioned thus far that when you come across a perform, you you exit the interpreter and you you go all the way out. um Handling an effect is where you you say, within this execution, um if an effect is thrown or raised, um I want to handle it with this this function so you're back into like internal code. um And this is best understood as try-catch, except you get to resume back to where you are. So an abort is an effect. um So you can have a ah handler that says, OK, if this effect is is it lifted, within this run block, run this code instead and continue. So you can write ah effects in terms of other effects by having, if the handler that catches your log effect then uses the right to file effect. The total program has only one effect, which is right to file.
00:20:32
Speaker
And the internal function can have the effect log because, so this is where the type checker knows what's happening. So all code, which is run inside a handle block is known that it can have, it can, they can raise the effect that's handled by the handle block. And then that total function does no longer have that effect. So if you handle the log effect. in a function in terms of the file effect in terms of yes. So if you have a function, which is

Prioritizing AST over Text Syntax

00:20:55
Speaker
say do something and it calls log, but you then call that function inside the context of a handler, the function outside this, this is getting waving my hands a lot, ah but the function outside this no longer has the log effect.
00:21:06
Speaker
Right. this this is starting to feel ramaer This is guiding me into the tricky things. This must have syntax, and yet I know you've tried to avoid having any syntax in your language. So how does that work and why? Well, so maybe I, so maybe I should expand on the, like, why there's no syntax. So yes, when I started this language, I wrote the AST by hand. Um, and I wanted the AST to be like the product, um, which, which people would come across. Like the AST was the thing you would find described. Um,
00:21:41
Speaker
And we're part of that for once, but yeah, we'll go into that. But like, so all that I need to have is handling and performing needs to have AST nodes. And if anyone ever wrote a syntax or I wrote a syntax, there would be a syntax corresponding to declaring an AST node for perform. You know, like when someone writes let in text in a text file, they get a let node. If someone writes the keyword perform. It could turn into a perform note in the um AST. So it's it's definitely part of the tree. Like it's it's not defined in terms of other parts of the language, like the language, even though it's just the AST has got a very explicit.
00:22:16
Speaker
syntax. I mean, I guess it is a syntax because it's in the abstract syntax tree. It just doesn't have a text syntax. But there is a very explicit syntax. There's an abstract syntax, not a concrete one, I guess. Yeah, it's just extra abstract. I really don't quite know what to call it at this point. It's a structured language because it's a non-textual language, essentially. It's a data definition for a language that could have sugar added on top. Yes. Right, yeah. But yeah, so there's no magic. It's definitely, yeah, they exist in the AST and they exist as a node called perform which has the label of what kind it is and then there's a node called handle which has the label of what kind of effects you're handling. I mean, this is unusual because if you're a language designer or a writer of papers, I can believe that you would just see a language as being primarily the AST.
00:23:04
Speaker
But as a day-to-day programmer, there's almost no mental separation between... I mean, the the syntax is the language for many people. Yeah, I think it's... um Well, this is one of the reasons why I think EYG is sort of interesting enough to be talked about on its own rather than just like a test bed for other things. um Because yeah, it's it's definitely unusual and it's... I think it's one of those assumptions maybe that programmers make that we don't talk about enough. Like, you know why is the language a text file? I mean, it's kind of a trope that people point out that Excel is a language or that you know there's drag and drop languages and so on and so forth. um But I was just intrigued by the idea of um
00:23:39
Speaker
making the AST like stable before the actual syntax, because a lot of language development, I mean, again, this was kind of related to my time watching Gleam develop was there was a lot of discussion about the syntax. And then there was an also a discussion that they didn't want to make the AST stable. They're like, that's an internal detail that I don't want people to rely upon, which is a perfectly acceptable way to develop things. But it means that if you want to make other tooling, you have to pass the text format. You have to write a pass or even if you want to write like a formatter or something else. So I was kind of hopeful that if you turn the relationship around, you could have more of an ecosystem of things based on the AST. I mean, one of my things I would love to have is an ecosystem of type checkers. So I've never done anything with linear types. But if someone has the AST and they just want to try writing
00:24:30
Speaker
you know like but As I mentioned, you can you can run the interpreter without type checking it. You can run the interpreter after running my type checker, which is a pretty good type checker, but you could get someone's better type checker, which looked at safe sizes, for example, like make sure that none of your binaries got too large and or you know linear types to make sure that resources were only used once or so on and so on. You could get more information with a better type checker. And that it would be much easier to exist because you would just feed it the AST. The AST is the thing which is kind of talked about and is the... Yeah. Yeah. And am I right in thinking that the linear type checking is all at the type checking phase, so it wouldn't affect your syntax tree to add that in?
00:25:11
Speaker
Well, it it it doesn't need to. um I think this is where we blur, the when you blur the boundary to to the the text file, to the AST, you're allowed to add and like annotations are something which an interestingly blur the line. Because if you have annotations in the AST, you're assuming that the annotations are like a type system that you're using. like underneath but you know you've You've made some assumption that like these types make sense for like the you know the in the annotation. um So currently I don't have a way to add type annotations to my program at all because I've wanted to have those completely like divorced from each other. um right so rus but isn't that going yes sorry so yeah So Rust has lifetimes, which is essentially is
00:25:50
Speaker
an annotation of their linear affine types. I'm not exactly sure which one they have, but yes, they've they've let it get into the AST once you had lifetimes. if they If all of the lifetime checking was implicit, then yes, it would be totally divorced.
00:26:05
Speaker
Doesn't that give you a problem with things like, surely you want to annotate the AST for the size of, eventually, the size of an integer such that it could be compiled efficiently to C? If you want to compile stuff efficiency, which is not a problem I've ever tackled yet. Oh, it's a non-goal. No, I think, yes, I think that's a fair point and I think, kind of, Like I like this project coming from a different direction, but it's, it's, there are things it's thrown away. And one of those is definitely, uh, the performance is actually not too bad because you don't need to pass text files. So it kind of beats some interpreted language, but it's not like performance is a non-goal at the moment. Um,

Prototyping and Collaboration with EYG

00:26:46
Speaker
there's, yeah. So is.
00:26:52
Speaker
There you are with, you've got you've got an abstract syntax tree, which seems to me to be fairly understandable. It's only like 20 odd nodes, right? yeah but and many of them are quite familiar. You've got a type checker, which by your own review is pretty good. ye And you've got an interpreter and a compiler. So if I were if i was sitting around as a wannabe language designer thinking I could get away with just writing a parser and use you as the engine of my language, would that be valid?
00:27:22
Speaker
Yeah, that would be that would actually be great because I am not interested in parses. I have written one because I've written a Lisp syntax because at some point at some point I realized writing my JSON by hand was causing a problem, ah but it wasn't a fun problem to me um in some ways. so yes you can I mean, I think you could do this with other other languages, like they would have a library of, you know, if a library, if a language has broken a module to the parser and the type checker and the backend, you could do that. But yes, I'm a much more like front and center with that's what I would like to happen. Um, you know, if you use EYG as this and I release a 1.0, I'm going to commit to the AST being stable. Whereas most other languages don't commit to anything from the AST.
00:28:04
Speaker
No, they commit to the syntax being stable-ish, right? Yeah. Yeah, OK. Because I quite like writing parsers. Well, I'll look forward to seeing yourself. OK. Maybe I should get stuck in. So there is another big part of this, which I know you mentioned, um which is, I'm just going to give you the headline, and you're going to explain what it means to me. So you've gone it all in on row types. yep What does that mean for a language? So the um so row types are, I mean, how I implement effect types. They're also how I implement almost everything else in the language. So this robust simplicity, um I mean, I did have a goal of EYG, which it's it's still a,
00:28:48
Speaker
stretch goal is that the install page should just be an instruction for writing your own interpreter and type checker. I love the idea that it could be, like it could exist like I like the idea that EYG could exist separate from any implementation, kind of pushing that thing of like, I have described the AST, I've used the fact that it's, um, like well thought through, like it's ergonomic. You can make useful things with it. Um, and you know, you can run your password and you can read my instructions, but you don't have to rely on like me, the like GitHub repo being up to date. Um, for example, uh, so yes, so like, so it'd be like language as spec or language as reference implementation or more languages spec. So the reference implementation becomes less and less important. Um,
00:29:33
Speaker
Yeah. Anyway, so yes, you asked about row types. I was a bit of an aside, but yeah, so the, um, row types are, they're a way of having extensible, uh, type data structures. So you can have, um, so extensible records, uh, would be, you know, like you can have a ah record where you say this has a ah name field and an age field, you know, make a record, but you can have a function which accepts anything with a name field. Um, so this is like. Yeah, I mean, I was about to say this is in in contrast to unextensible, which doesn't really mean anything. But yeah, so extensible records, um they can be larger than what you call them into. And these are also importantly structural. So anything that has a name field um can be passed into this function. So you know there's um there's this concept of a type, which is I have a record, which has a field of name, which is string, and maybe other fields. And so that ah label and type, so the label being name and the type being string, is a row.
00:30:27
Speaker
Um, and so this is, I think they're called row types due to the fact you can start rolling this down to kind of database approaches. So you can have like these tables have these fields. And so, you know, I can work on any table, which has a field called name, even if there are other tables. so yeah so that right yeah so that's You could almost think of it as columnar-based type definition. I think you could. I think... um I find row types quite... Like getting through them took me a little ah little while to understand. I find them actually quite foundational to the point where it's quite tricky to explain them in terms of other things.
00:31:04
Speaker
you know it's I can explain that if you have an extensible record, it's a row type underneath. You can also have extensible enums. So if you have a function or a case statement which says switch on um you know red, green, or blue, And you have a type which is just red or green. You can obviously pass that in because it's it's extensible in the things that it does. It's like it's the inverse at that point. um And so effects are also row types because if you have a handler which handles the log type, and
00:31:35
Speaker
The function that it can be passed in is okay. So this function inside here must be something that raises the effect log with the type that I handled. So let's assume string and any other effect, which is then bubble to the outside. So this is how the effects are allowed to compose. So this. OK, so i let me think if I can make this concrete. So I've got i've got a function that can take a user, and I realize that function only actually uses the user's name field. So I changed the type of my function to accept any type that includes a name field and potentially other stuff. Yes.
00:32:15
Speaker
right And then similarly for effects, I declare the effect it has and it begins as an empty thing that has no fields and that's pure. And I add in logging and now it's an extra field and then I can add in more fields yeah like ah needs the internet as well as logging. And you don't need to define them upfront. So if you have a block of code where one place you use the internet and later you use um logging, like it's it's smart enough to just make a ah combination of those two rows. without you having to say this function will do internet and, uh, logging. Um, and I think this is important for the ergonomics of it, because actually if you make everything need effects, including random numbers, it would be a real pain if deep in your code, you wanted a random number and you had to kind of change the type all the way up your call stack going, okay, this is function is now got these effects plus random. You know, you have to.
00:33:09
Speaker
Yes. And yeah, I'm thinking Haskell really suffered from this in the early days and they've been exploring different tricks to get around it ever since. Yes. Well, I never like to say Haskell can't do something because it's almost never true because people have got such and such an extension. um But if you use like monads, if you have a monadic thing, then you and you do like monads don't compose very well. So this is where you need your monad transformers. So if you have sort of syntax sugar to work with, uh, say list, you know, you have some kind of list, um, going through lists, a comprehension. If you have a list comprehension syntax, but you also want to deal with async stuff and you have a nice like async await, if you represent those as monads, you then eventually need a monad for, okay, I need the list async monad. And I also need the async list monads. Um, so yeah, this is a way to not have to deal with that.
00:33:57
Speaker
Right, yeah, yeah, I can see that. um Because in the end it's just all treating it as sets of stuff which can be added to. yes we had um We had Jose Valim talking about Elixa's new type checker and he was saying that it seems like at some point type checking just becomes about checking this set of things against that set of things and see if they intersect. Uh, yeah, as I think if people really know the types calling row type sets is incorrect, but it definitely works for my fairly. Yeah, it would definitely work for my intuition. Yes. The, the set of effects that this function throws and the set of effects that this runtime handles fit inside each other. So it's good to, it's good to continue.
00:34:42
Speaker
Right. So when you mentioned async stuff, that made me wonder, so if I wanted, if I were there writing my parser and pretending I've written a whole language because I'm really piggybacking off you, and I want to add async await syntax, does that really boil down to figuring out how to turn my keyword sugar into something in your abstract abstract syntax tree, which will probably be like an effect? Yeah, absolutely. you can write um yeah So if you had a syntax sugar for perform, you know so you use the keyword perform and X, so you could perform any effect. Yes, if you had an effect for a weight, um then you'd just make a node which was perform, then the string value async and just make a special one. um okay There would be no difference in behavior to handle the async string versus a special
00:35:32
Speaker
Uh, one, and it's quite nice because actually my, my ah interpreter is in JavaScript. So I have the problem of function, function coloring, and I actually. Like I don't have function coloring inside my language, even though it's running on a JavaScript runtime, like it gets rid of it. Back up a second, explain function coloring. So function coloring is um the idea that, well, I mean, it's I think it just, other languages have it, but it all seems to be about the ah JavaScript await, async await syntax. So um if you await something, um you have to call that await inside an async function.
00:36:10
Speaker
Because obviously you can't, like if a function is not marked as async, it must return immediately. But if you're awaiting something, you can't return immediately. Um, so to call this async function in some parent code, you need to use the await keyword. And so this, this coloring idea is like once you have been colored with the async or await keyword, it bubbles through to the rest of your program. Whereas with effect types, you don't have this problem.
00:36:37
Speaker
because you you just define a handler at the point that you want to eliminate that fact. well you don't you actually Even without eliminating it, it doesn't happen because the type system is you don't have a... So in async code, you return, say, a promise of a number. So that's a different value that you're passing around. Whereas if you have some code that returns that does async in the effects, the return type is just a number. So you you do the actual return value is a number. It's not a promise of a number. And your runtime has to handle. Right. Yeah. So as mentioned, when it suspends, if you do something asynchronous, you suspend the whole interpret, you know, interpretation of the program and you just resume when you've got it, but the program is kind of not part of that kind discussion.
00:37:23
Speaker
Right. Right. Yeah. I think I see how that works. Okay. So where do I get? I'm tempted. I know I don't have the spare time, but still I'm tempted, so I'm allowed to ask. ah Because um I'm sure I come up with an interesting syntax and a compiler. Where would I get started and how much of a language would I get from your syntax tree? um So where you'd get started is, um i think I think a good way to get started is kind of, so the AST is for a functional pure language. So there's nothing in the AST that allows you to like,
00:38:01
Speaker
mutator previously defined variables. So you have to you know assume that you're okay with all of those semantics. um But assuming that, I think you kind of just start designing the language you want and writing a parser and you just have to make a data type that corresponds to my AST. So currently my AST is defined in Gleam, so you could pull it in as a library if you were writing in Gleam, but you could also just you know, translate um translate that to whatever language you wanted. So you'd have a data type, which is like, okay, I've got a variable node, I've got a let node, I've got a perform node. And as long as you've made a ah tree of those things.
00:38:38
Speaker
you would be fine. and So I have a JSON format for my AST, which is how you could pass it between languages. So you could write your parser, build that data structure, and then you'd have to write the serialized JSON from your language. And then you could load it up with the um decode function I have and then run all of my tooling over it. and so what you would get is you would have you would have You'd have a sort of ready functional language you'd have ah so that the type system is guaranteed to be total. like you don't know it's It never crashes if you pass. Theoretically, I'm sure. my it's i'm sure but um It's not 1.0 yet, so I'm sure you can find a situation where my type checker doesn't get it right, but it like the theory is it covers every use case.
00:39:18
Speaker
um The thing you might find is explain saying by design it won't crash yes um the thing that gets interesting is the syntax sugar. Like when you want to ah find something more compound, like say you want um pattern matching is an interesting example. Like you want to syntax, you know, like in JavaScript where you can, you can say let open curly braces because I want to de-structure this record as a single syntactic unit. um You would have to work out when you pass that how to make it into the lower level of the AST. So I don't have that concept of destructuring. I just have a lot of assignments. and So you would have to turn that um syntactic unit into, okay, first I have a temporary variable, which is you know the result of the thing on the right of the equals. Then I declare another variable. So you'd you'd have to make quite a big tree of nodes, what might be one syntactic unit in your program.
00:40:13
Speaker
Okay, but presumably I can see that probably wouldn't be too hard. I mean if I'm saying like, destructure the name and age of a user object, I just say let x equal user object, let name equal x dot.name. I basically expand it into three let statements. yeah that's i mean I don't think there's any ah upper limit to how much you might want to add on top of it. People people get very opinionated on syntax. Maybe you want a kind of question mark operator. operator you know you' you've You've done some kind of assumption on it being ah a tagged result. type like there's Yes, in the case we've example there, you can but you can definitely get started and level up. If you write something that looks like a very simple lambda calculus, it's a very simple job, and you can you can add them one at a time.
00:40:59
Speaker
OK. Yeah, because i don't have to I don't have to generate all the possible nodes and worry about that straight away. Yeah, you you could have it you could have okay stuff not in the syntax tree. You could ignore, for example, the effects to begin with. You could just write your calculator and only have numbers and just not be able to create strings or effects or handlers. And just they would never appear in the tree. But the the type checker would still work. OK, I could do that easily enough. Then the question is, if Where are you heading with it? If you're not heading out into developer space by writing your own syntax, where are you trying to take this?
00:41:37
Speaker
So I have written um a structural editor on this. So this was, I've actually written three structural editors in various generations ah because it's not too hard a ah ah a job. So the parsing that we've just talked about is all about producing this AST data structure. You can have an interactive way of producing AST. So you know the the the structured editor works by, you you start it up in the browser and it gives you a data structure, which is just you know the number zero. And you you make keyboard shortcuts. So like I have a keyboard shortcut for E for equals, and it takes whatever you have and assumes you want it under assignments. So your AST now becomes you know X equals the number zero. And then after it, I have ah i have this node called vacant, which is a program you've not written yet. um And so this allows you to do
00:42:28
Speaker
that much quicker, um, and various languages have tried structural editors. I think, um, I think rock was started with this, but I think they have, I don't know if they abandoned it or they sort of parked it as a like after pope like 1.0 thing, but my hope is that the really kind of driving the minimal amount of the AST makes this all simpler. I get, I'm hoping that you can actually have a structured editor much quicker. And so I've been exploring this space for, I mean, actually in some cases almost like end user programming because you have you know developers like the text file because they like Git and a variety of other things. um But yeah, a sort of a community of ways to produce the AST and also a community of ways to use the AST.
00:43:13
Speaker
um Like the whole thing is much more like a kind of rules engine toolkit. You know, you can run it. It's like I've extracted it from the machine. Um, so, you know, I have this interpreter, but I also have this JavaScript, uh, Transpiler. I also have a Go Transpiler, which I wrote, which I then compiled through TinyGo. So I've run a functional language with ah managed effects on an Arduino. No way. um you know i i only The only effect I had was I think, yeah, yeah. So i had what the effect was LED pin. And so you had type checking that this is the code that updates the pin in the LEDs. So you you flash you've had type checking to access of the pins.
00:43:51
Speaker
Um, but yeah, that's the kind of way I want the the things to go. So, I mean, it's a joke to have the run anywhere, but running anywhere is like, it's not run anywhere because I have solved the problem. It's running anywhere because you can solve the problem incredibly quickly. You know, if you wanted to write a small amount of a Ruby program in EYG, you wanted like different type guarantees, you have to write the interpretation. I mean, okay, that a Ruby interpreter is going to be very slow. We start with Ruby slowness and we multiply it by, you know, tree walking interpreter slowness. Um, but you can, and you can do that quite quickly. um so yeah it's a kind of yeah I think it's it's a different axis of contributions that I'm trying to explore is maybe a way of putting it. I would love yeah i' love a visual language on top of it. like if you make the as If you print the AST in lines and boxes, but you make the same AST, you can sit there with, I don't know, a Lisp syntax in whatever you want, and we can like throw it over the wire and put on a like box and wire syntax.
00:44:49
Speaker
And some amount of the syntactic sugar is where you start losing information, but you could potentially collaborate across this boundary. and Oh, so you you'd end up in a place where one person was editing the code as text and another as a drag and drop GUI type thing for the same code base. Yeah, potentially.

Future of EYG and Platform Expansion

00:45:10
Speaker
That would be very curious. Yeah.
00:45:14
Speaker
The key thing is you have to not be too tied to the syntax sugar. So that's why it's such a design goal to make the AST like useful in itself. right yeah So for example, the destructuring. If you come up with a horrible variable name, like underscore underscore special sugar for Chris's parser. before you then pull off the name of the age field in the example that we were talking about. ah My structural editor doesn't have that sugar, I'm just going to see that and go, oh, that's a weird name. and But that's the only difficulty between having, essentially, choose your own syntax, choose your own editor. What this makes me think is, I'm thinking the JVM and Wasm as two ways of making something portable by making portable hardware.
00:45:54
Speaker
by focusing on a pretend piece of hardware that you can move around. You're almost doing something very similar, but making the AST your core thing we move around from platform to platform.
00:46:09
Speaker
Yes, if I wanted to be very ambitious, which i mean I'd like to one day, yes, this is an alternative approach to something like Wasm for kind of portability. You could have your script tag for running this um in the browser, you know I mean a big CDN so everyone has the same one. And then yes, you you ship around bits of the AST for other people to run. so yeah and i think Yeah, I mean, to do this, there are certain things you can do to make this fast-ish, like having a nice compact format of the AST. Like I mentioned that it can be faster because you don't have to pass source code. Obviously, JSON is not the most efficient format either. um But yes, if I came up with a compact binary format for the AST, you would be able to jump around in it much more efficiently.
00:46:51
Speaker
Um, then, you know, passing syntax, like passing source code, but it still has all the information that you can load up somewhere else. Yeah. Yeah. And you get, because you've got type checked effect, you could very easily construct sandbox is around the code that you're trying to execute. Someone sent you some random code and you can guarantee. Yes, there's no way to represents.
00:47:15
Speaker
Absolutely. Yeah. And you can, you can see, I mean, that I think effects of security is true in the like most kind of hammer, there are no effects sort of way. Obviously, if you see that it has the effect to talk to internet, ah you you have to be a little bit more. careful about it, but you can ah there are tricks you can do where you can have an effect so that you could say, talk to the internet, but you then have ah an enum of kind of known domains that you're allowed to talk to. So instead of just throwing the effect fetch with a web request, you could throw the effect fetch with say, I don't know, I use DNS simple. So like DNS simple comma like path, and there's no way to throw an effect that isn't considered a path under the DNS simple origin, which allows you to switch between say, their their sandbox and production environment. yeah yeah and
00:47:59
Speaker
so you yeah but like yeah it's it's It's tricky with the security because file read is another good one. File read with any string. well Which file do you want to read? yeah but You might read any of them once I give you that content. You could imagine publishing a sub-spec of your AST that included the one effect. You can read them right to this small five meg sandbox.
00:48:20
Speaker
And you can also, i one of the things I quite like about it is it actually incentivizes you to do it properly. So if you, if you have library code that um you want more people to use, probably having it have less effects, is that genuinely better? Like, you know, you would, you would be inclined to pick up libraries. So if you had a date passing library, I don't want it to like check the database of like pre, you know, it's like, just put it all in the code. But once it becomes pure, I know that it's all that like date so time zones in that library. You know, it's not, it's not checking them somewhere else. Yeah. Yeah. And I would rather use that library. I can see that. Okay. and Okay. So they the practicalities of this, I haven't asked and I have to ask, why did you choose gleam to write it in?
00:49:08
Speaker
So Gleam is good for incredibly fast prototyping. So Gleam is definitely my favorite language when I've got text files open. um so I also um like targeting the web a lot. i think that um like ewa g at the moment is currently Obviously there's a lot of, it its feature is these effects. So it's valuable in programs where you do a lot of effects. So kind of writing you know automation scripts, like there's an effect for reaching my calendar or like, you know, the geo API, that's where it's strong. um And it's not strong, or at least it's not targeting, you know, writing 10,000 lines of missus logic code internally, because there's no benefit to the effects. Like it's it's just any other functional language at that point.
00:49:53
Speaker
um So, yeah, so Gleam gave me the ability to move very fast because it's, you know, it is fully type checked. And then the compiling to JavaScript that Gleam does allowed me to like make web sandboxes. So yeah, that was just a good place to start. Okay. Okay. Yep. I can see that. It's a nice, fun, and well, well-specced language to work with. Do you? I think there would like, there would definitely be a day when I have um a fast interpreter. That needs to exist. Yeah. And that won't be claimed. There'll be something, I think, I think Zig or something, like I would like that to happen one day. Do you think it's feasible for someone someone to come along and create that without you?
00:50:37
Speaker
I think... ah which You mean that interpreter another and other environments? Yeah, yeah i' i'm just I guess I'm testing the boundaries of how much the AST is your core product. Could someone take that AST off the shelf and say, right, I'm interpreting DIG today because Chris has got a syntax that will compile to it and we'll will never actually touch Peter's code.
00:51:00
Speaker
Definitely. I think the only thing that would be the only, the only difficulty is, well, I mean, there isn't any, yes, that's, that's fine. That's the goal. I mean, how good is my documentation? Not that many people have tried it, but I'm improving that as the kind of thing that I want to sell. Um, but I, I, I think I said, I liked the idea that installing it would be an instruction on how to write an interpreter. Like if the homepage was, this is how you write an interpreter. Um, I think you could write an interpreter in a weekend for it quite happily. Um,
00:51:27
Speaker
and What would you need? would i mean I think I managed to write an interpreter and go for it in two hours. Two hours? Okay, so you're an expert but that's still fast. Is there anything you'd need in the host language you're interpreting?
00:51:42
Speaker
Well, in the host language, and no, you don't need anything specific in the host language. There's nothing in what you need. Um, there's nothing special about the data structure. I mean, an AST is ah a big enum. So, you know, the fact that Go doesn't have enums makes that a little tricky. Um, but there's nothing you need specifically. The reason it's fast is because you can choose the hard stuff and the hard stuff is integrating to the outside world. So your first interpreter doesn't have any effects. So you can write, you know, you can only run pure programs, but it is complete. You do have an interpreter by the end of that. And then you go, ah, I have found a program that needs logging effects. So I will implement just the logger. And your first version of the logger could just, you know, use the native logger or throw away the logs, but like, that's kind of where the art of like your runtime comes from. But getting to the point where you have the first thing for pure programs, very easy. Okay.
00:52:33
Speaker
Will you be adding things like, again, to test the boundaries of this? Will you be planning to add something like LSP as a way of inspecting and manipulating the abstract syntax tree?
00:52:49
Speaker
So I think, ah so LSP, so the language server protocol, I think, so I think, so that's a way of integrating with text editors. So I think I don't have a reason to do that because i think i don't think if i i don't think i if I think if I don't have a text syntax, there's nothing to integrate to um because there's no cursor position. So my structured editor can't use LSP because everything's about where you are in the text. I think if you wrote a parser, You would be the person who would be interested in writing an LSP for your particular syntax. But I will back on. I don't think that there's a syntax free approach. There are some parts of it though, aren't there? So an LSP server can support refactoring and that feels very much like your domain of the AST.
00:53:37
Speaker
Yeah, I think you could have a library of um kind of tree manipulation things in the AST. So in my structured editor, you know I have the ability to move an assignment up or down. So you know you switch let x equals one and let y with switching the other way around. And that's ah that's a transformation I have a library function for. um So I think having a ah richer and richer library of those would be a very useful toolkit for someone to help, but I think the actual interface to LSP would be tied to a syntax. OK, yeah. I could just maybe help out more in terms of a library of transforms. So I have to write the LSP, but I lean into your toolkit.
00:54:14
Speaker
Yes, and right now that means you have to write it in Gleam, because it's it's all Gleam library code. OK, fair enough. So just that excuse me to step back out a second, What state are you in right now? Where do you want to get to and how are you supporting this project?
00:54:35
Speaker
So the state right now is it's definitely um but it's it's It's kind of rapidly improving the documentation. this is This is what I'm spending time doing right now. So I'm currently on a on a work rate, which has given me a six month period to essentially try and start my own business in Sweden and starting some stuff around this and kind of no-code tooling is what I've done. And so EYG is a big part of experimenting in this space. um So yeah, so like where we are right now is, i you know, I want, I want people to be able to write that interpreter. That's kind of a test that the documentation is good enough. Um, there's a, there's a format for the AST, which I have for like doing hash references. So you can take the AST and you can hash it into a, okay, this hash references this piece of code. Um, okay. yeah And I would like people to share those hashes.
00:55:25
Speaker
essentially. So I have two key goals. One is people share a hash of some like code they wrote with someone else, because that's a test of kind of playing with the concept of a packet manager. And the other goal is for someone to write an interpreter or a compiler, whichever they want to, for an environment that I haven't yet supported. So someone beating me to the Zig interpreter would be a wonderful thing to discover, or someone putting it in a really random environment. So I mentioned that I put it on Arduino, but if someone I discovered there was a rules engine in Jira. like i like you know the effect that You could have a program where the only effect is up your ticket status, so you can push it into places which are a bit unconventional. right yeah but yeah so The goal is a ah community or something of like a collection of runtimes.
00:56:10
Speaker
is is what I want to get to. but I've got this six month period to like get it over the hump so that it's then rolling. um So yeah, so I'm supporting it from just that time off. like you know I get paid again in November or December. So hopefully by that time it's it's found a groove to kind of roll up to 1.0 maybe some point after. OK. So it could be in the future that this is the but kind of off the shelf place you go to if you want to roll a site specific programming language that could be as heavily functionally typed as it wishes to be. I think so. Yeah, I think that would be a place we could definitely get to. That would be pretty cool.
00:56:49
Speaker
ah Well, I know we've got a lot of Zig fans who listen to the podcast, so maybe someone will take you up on that offer. That would be nice, because it's it's ah it's a thing I want to do, but I've written almost no Zig. They say all the right things about Zig, and I feel people's enthusiasm for it, but I haven't had time to... Yeah, I have to limit myself to not wanting to pick up another runtime for it right now, until I think the the top features are finished. Yeah. what What is the next thing you're going to implement? The thing I'm implementing right now is a way to pull in someone else's code. So to actually start having ah some kind of package ecosystem um where you can.
00:57:26
Speaker
Yeah. I need a format for which the code is not just a JSON blob with all the code you ever had. There needs to be a way to say, pull in like standard library, for example, there is a standard library, but it's, you have to know where to find it. Um, and there's no way for other people to have their own, uh, sort of packages shared. It's all based on like copy paste it into your AST. So I need to, but that's probably mostly done. Like I'm hoping kind of like the next couple of weeks, there will be an answer for this is how we address pieces of code that you can pull in. okay Okay, well this pog this recording will be published in a few weeks so we'll see if we can you can beat me to writing the show notes and we'll put a link in if you have. Cool, challenge accepted. but Yeah, cool. so So you're in that interesting cusp of the language works and now you're trying to branch out into the rest of the world. Yeah, it's a very exciting time to see what people want to do with it because
00:58:21
Speaker
ah yeah I've been trying to show it to a very diverse group of people and so that ah you know some people go, this is a very slow way to implement a language. And I'm like, okay, we're going to talk quite a bit later. And some people are like, oh, I have terrible time with this unusual environment and I wish my you know compiler told me that I could update Jira tickets. I'm like, okay, yes, you and me should talk a lot more. I wonder if you'll find friends at banks because they often have weird languages that they have to either write or compile to. Well, I found some friends in um industrial automation actually. That's where I've previously been working in. So the yeah the robot control software, the industrial machinery software, they are specific languages. Quite a lot of them come from like Siemens. You know you don't think of them as like, they're not on Hacker News telling you about the language they've created.
00:59:11
Speaker
But yes, they exist and they don't need to move very fast. So they are quite clunky languages So yeah playing in those spaces are very interesting Yeah, yeah that actually sounds like fun and you could end up with With a robot being controlled by a seriously advanced well-researched language one day. Hopefully. Well, hopefully not too soon if it's not too big a robot
00:59:37
Speaker
Yeah, well well, hopefully someone takes you up on that offer and we'll see how things evolve. But for now, thanks very much for joining me. Yeah, cheers. Thanks. Peter, thanks very much. I'll put links to EYG in the show notes, of course, and as Peter explained, there's a JavaScript interpreter for EYG, so you can actually run the code in the browser, go to the homepage, and you can play around with it. It's quite fun, just bear in mind that the syntax is, of course, optional, so if you don't like that part, you it's up to you to change it.
01:00:10
Speaker
Before you go, if you've enjoyed this discussion, please give it a like or a review or a rating, depending on which app you're using to get this episode. If you want to support future episodes, please consider supporting me on Patreon. There's a link to the Developer Voices Patreon in the show notes as well. And if you're currently supporting, thank you very much. Oh, and make sure you're subscribed because of course we'll be back next week with another episode. which may be a bit chaotic. As I'm recording this, it's now entering into summer holiday vacation season, so the release schedule might get a little chaotic for the next few weeks. Hopefully not, but bear with me if it does. In the meantime, as we wait to find out quite how chaotic the coming weeks are going to be, I've been your host, Chris Jenkins. This has been Developer Voices with Peter Saxton. Thanks for listening.