elm-codegen with Matthew Griffith

Matthew Griffith joins us to discuss elm-codegen, a tool for generating Elm safely and conveniently.
September 26, 2022


Hello, Jeroen.
Hello, Dillon.
You know what I love to generate?
Good content?
I do love generating good content.
I love generating ideas.
I love generating code.
And I love generating buzz.
And you know who's really good for generating buzz?
It's our good friend Matt Griffith.
Matt, thanks for joining the show.
Oh, man.
Thanks for having me.
That's probably the best introduction we've done so far.
That was pretty good.
Yeah, it did not go where I thought it was going to go.
That's great.
Could the editing team please add bells and whistles to...
Yeah, right.
Like, cheers in the background.
Matt, I think this is a long time coming.
We've had you on for some game show episodes, but not for a topic.
So here we are.
Yeah, here we go.
I'm excited.
Yeah, I'm glad to be here.
This is awesome.
Yeah, I'm excited about your cranking out some projects right now.
So what are we talking about today?
Yeah, there's been a lot on the back burner for a long time.
So now it's like, okay, time to get them out.
Like, let's do this.
So what are we talking about today?
Yeah, let's talk about it.
We are talking about Elm CodeGen, which is the first of these projects
that needs to get out there.
Yeah, shall I give like a high level overview of what it does?
Yeah, that would be excellent.
What the heck is it?
Yeah, so Elm CodeGen.
It's two things, actually.
So it's a command line tool, right?
And it's also an Elm library, and you can use either of them.
And essentially what I wanted is I wanted something to generate Elm code
that was more convenient than string templates
and less of a pain than ASTs.
I realized that last point might be like a little bit of a spicy point,
and there's some interesting like kind of area to navigate there.
ASTs are great, but like the question was, could we do better?
And specifically, I was like, okay, if when someone wants to do CodeGen
for like a tiny thing, if they reach for this versus just reaching
for a string template, like string replace, right?
If they believe just sort of in their gut that that's more convenient,
then I've won.
And separately, if you could build some sort of larger project,
something very more abstract, and have it be more maintainable
than having to juggle a raw AST, that would be a major win.
And if I could do both, it would be just a very cool project.
I feel a bit confused.
Like, do you know about any code generation tools that use ASTs
where you actually have to construct ASTs to generate code?
I think it would be the default.
I think, yeah, for sure.
Like if you go to the TypeScript compiler, like to generate TypeScript code,
you have a little factory.
Oh, yeah.
But like it's a very open ended, you know, there's not much judge there, right?
It's like, here are all the options for this AST thing,
and you just have sort of this massive surface area, right?
Yeah, I remember playing with it with LMMtimized Level 2.
Right, that was my experience as well, right?
We're trying to like, and there's maybe like two aspects to this
because there's reading ASTs, right, which is its own certain level of complexity.
And there's writing them, which has its own separate set of complexities.
So, okay, maybe this is a good point to sort of,
because it's easy to jump into the details here.
Like maybe the motivating examples,
because I didn't actually set out to make a code gen tool,
or like that's not where it started, right?
So where it started and what I was kind of interested in is there were two projects,
one for work and one that I just wanted out there,
but maybe relevant to work that I wanted.
It's been on my mind for a while.
And one of them was, I was really curious to build GQL 2.0 sort of compiler, right?
Read a little GQL query or mutation,
generate some really nice Elm code that you could interact with.
So, and there's a whole discussion there that I know Dillon will have.
Yes, we would love to have you on for that.
And just to clarify for anyone who doesn't know GQL, you're talking about GraphQL.
GraphQL, that's right.
That's right.
So there's like, okay, I want to be able to generate some code,
like reading essentially a query,
maybe do some checking and generate some Elm code.
That's a decently complex operation actually.
So it's kind of a pretty abstract use case.
It's not quite as abstract as maybe your own,
you're used to dealing with Elm review being like Elm review fix of like,
we're going to map a full AST to a full AST, right?
Here we're taking a smaller input.
It's not that much smaller, but it is smaller.
It's the AST of your GraphQL, either your query or the schema,
maybe doing some things like checking,
type checking it saying like, okay, like you're only asking for fields you expect
and then generating some Elm code.
So you're still getting the AST on the generation side.
Just to clarify Elm review doesn't work with,
you don't give it an AST, you manipulate the strings.
Fixes are just changing, inserting, deleting strings, which is...
That's interesting because it's not, you're using Elm syntax though, right?
But if you replace some parts of the AST by other AST,
then you lose formatting, you lose comments.
Okay, fair.
And the thing is if you do it with strings, string manipulations,
then that's easier to understand for other tools like a language server.
And you can still use AST replacements by having an AST to string function.
Yeah, like a lot of people use Elm syntax DSL to generate a string that...
And then use the string as interrupt format kind of a thing.
That's really interesting.
I did not know that.
And then the other project that I was interested in to sort of anchor it here
was I was interested in generating like design systems for Elm UI.
I think with Elm UI, if you're not familiar with Elm UI,
it's a little sort of library for like a layout language that you can use instead of CSS
that basically will generate some CSS,
but maybe has some characteristics which make it easier to think about.
But one of the missing pieces I found with Elm UI, I'm like, okay, well, you know,
but what do I want with Elm UI?
I really want people to...
I want the experience to be they, you know, they're like, I would like to use Elm UI.
And then immediately they have...
They maybe make a few high level choices of like, I would like pastels, right?
And I would like a dark mode and I prefer like a serif font, right?
That's tastefully chosen.
And if they could just have a set of Elm code that was like, okay, well, you know,
here's a nice color palette like that we know works together.
Like here's a nice set of a spacing scale, like for like, you know,
paddings, like to have paddings and like font sizes and spacing between elements,
all sort of just naturally work out.
Like creating design systems, there are certain set of rules that are pretty well
known at this point.
So you can actually have a fairly constrained set of inputs and generate
something where it's like, okay, great.
You just have a design system now and you can interact with it and you can have a
beautiful app kind of right out of the gate.
That was something that I think the JavaScript community has more tools that
are like that, where you have something beautiful out of the gate.
Elm UI is not that.
It's actually like it's more about the logic of your layout, which is
intellectually really cool.
But ultimately, to thread the needle, you want somewhere it's like, okay,
they've made something and it's beautiful and hopefully even more beautiful than
what they would do just, you know, if they were left to their own devices or whatever.
So where would Elm CodeGen fit into this exactly?
So for that, it would be you create some sort of higher level design language,
like your color palette, spacing palette.
The term, like to think about this language, you would look to terms that
designers use to sort of describe design systems.
And then you would generate a bunch of Elm code and you would kind of have these
values baked in and then you can get this sort of, it's not really a guarantee,
but maybe like a sign, really a soft guarantee.
But it's like if you're only pulling from this generated, you know, UI theme
file, you have this pretty good thing of like, you know, when you're straying
outside of your design system and you know that if you use things from this
file, it's just going to or set of files, it's just there are just going to work.
So again, so it's taking like, and with CodeGen, I like to think of like, yeah,
inputs and outputs.
So GQL, it was the GraphQL schema and your queries and mutations to Elm code.
In this case, it would be like some high level design language.
Let's be fair, I haven't done this yet, right?
But it's more just in conceptual phases.
And then you take that and you'd be able to bake in these values into some
generated Elm code.
So those were kind of, and also just I found that there were tiny little CodeGen,
you know, tasks that I'm like, oh, if I could generate some code, this would
actually be really, really nice.
Like, but this is such a small thing as this even like a project.
And I'm like, and I've noticed that I think other people have these ideas too.
And so having it, so an example of this is like, well, you know, we at Vendor,
we, you know, we have our design team and they're working on stuff and the
design team lives in Figma mostly.
And so they're creating artifacts and everything.
And one of the things we found was, well, we have our own set of icons there
and we wanted to be able to have our icons and color them dynamically and,
you know, programmatically, right?
So it's like, okay, we want the X, but we want to turn it red or like,
you know, whatever.
And so it's like, well, that's actually like, how great would it be to just
make a Figma API call and say like, hey, give me all the things in this like
little box, like that have been squared, like that are inside this physical,
I forget what Figma calls things, but like that are all grouped together
in some sort of way, give me all of them.
And I'm going to create a Figma.icons file and of the way to reference it in
Elm code and maybe a few like little options I can switch.
I want the large version, I want the small version.
I would like the, you know, success versus failure colors or something like that.
So little, little deep, like little things where it's like, okay, someone
could build a common tool for everybody to do that.
And that's fine, but it's less of a project that, I mean, maybe that would be like,
maybe there's a general project there, but I'd love to be able to, it's like,
oh, it's very easy just to whip something like that up.
I'll take a day, I'm just going to do it.
And then we never have to worry about it.
Yeah, it's really, really exciting.
So I think I'm hearing like that I'm hearing two sides of this equation.
Like one is you're talking about having a more constrained way to generate code
to make it feel safer and easier.
So like doing string templating works, but it's, you can create invalid ASTs
using things AST based things like Elm C syntax DSL constraints that further.
So you can only generate valid ASTs, but valid ASTs might be invalid programs.
And then Elm code gen helps reduce that space a little further.
And so we haven't gotten into this, but with the help of sort of some generated
placeholders for installing quote unquote, installing Elm packages and it sort of
knowing the APIs and giving you generated code, meta generated code.
Yodog, I heard you like generated code.
Basically it gives you generated code to build generated code in a safer way.
So you're, so on the one side you're constraining things more to reduce friction.
And on the other side, you're giving like a CLI tool because it's kind of a pain to,
to build up the boilerplate for, okay, I need to run a little node JS application.
I need to spin up my own code.
I need to template some code.
I need to output that to a file.
And you have a CLI that helps you just kind of like Elm review.
You have a review config.
In code gen you have your generate.elm file and you run Elm code gen run and it generates Elm files.
Yeah, that's, that's right.
There's so the way I think about it and it's great.
This is like a preview of my strange loop talk.
So there we go.
I was literally writing slides this morning about this.
So you think about like a, a sliding scale of, of safety.
So on the left, right, you have strings, right?
And the only thing that strings are guarding against are, is that you're encoded in like, you know, UTF 8.
And even then, maybe not, I don't know.
Even you go with just numbers, just one giant string of, of.
Yeah, right.
Strings like so, but inherently flexible and you can build as is obvious if you were, you know,
trapped in a room and you needed to build a tiny little like cogen thing and you had nothing else,
you'd probably like have strings and do some replacements or what.
It's powerful.
Are these nightmares that you have being trapped in a room?
I have very, very weird.
The saw version of, of generating code.
To get out of this room, you have to build a cogen tool.
Oh, good.
I know how to do that.
Basically. Yeah.
So, so, okay.
So strings are on the left and they are the least safe, but immediately this convenience, like sliding scale is both talking about safety,
but it's also talking about convenience.
There's like this strings inherently portable, inherently convenience.
There's some safety stuff, but like you may or may not care about that depending on your use case.
Like, so it's like, okay, cool.
And just to like acknowledge that that's a valid choice.
It's not like more abstract is not always better.
There's there are trade offs always.
So then you go one, one notch further and you're like, okay, great.
I have an AST, an abstract syntax tree.
How cool is that?
Like we've now modeled the syntax in a tree and what we're guarding against is it's syntactically valid, but it's like, okay, that's cool.
We're still missing a few things.
One AST is tend to have a very large surface area.
Elm is actually pretty great because its surface area is smaller, but even for Elm, it's so small within AST.
It's actually still pretty big just because AST is necessarily sort of need to represent a base number of constructs unless it's a very, very simple language.
So it's like, okay, even in the simple case, it's still a lot of surface area.
And you like, so let's go one notch further.
How can we get more safety?
Like from an AST.
It's like, well, we have to sort of protect it a little bit and we have to maybe process it a little bit.
So now we're getting into the area where we're actually going to pretend to be like a little bit more of a compiler ourselves.
I'll look at the AST and do some stuff.
So the first thing is like, well, can we get imports to always work?
Like if you're generating code, if you want to start using a value and it's from a different module, how do you like?
It's like, well, before you have to think like, okay, I'm generating and you like this especially happened for the Elm GQL thing that I've been working on is like, okay, this file is going to reference that code and I'm going to like call this value or whatever.
I mean, like, okay, well, I'm going to make the call and then I'm separately and it's total other part of the code base.
I'm going to make sure that it's imported.
And you sort of sit there and you're like, well, but that's kind of dumb because I already like the AST has that information.
It's not even like that hidden, right?
So can I just before rendering, can I just grab like what values did you use?
I'm going to assume that they exist and I'm just going to import them.
So it's like, okay, that's one notch farther than an AST.
We've processed our AST a little bit to give us some safety.
We have this thing of like, you can't screw up your imports.
Like that's cool.
Okay, but and like, how can we go farther?
And this is where things change.
Like where I think the key insight for Elm Cogen kind of came where I think what makes it much more useful.
Like if I would have stopped there, I think it would have been pretty cool.
But there's a lot more value still on the table, which is like, okay.
I want to call a function from another module.
Or I want to generate.
I have to be careful because it's like we're getting into inception land where it's like, okay, are you talking about the code?
Are you talking about the code that generates the code?
You need to be more meta.
So I want to generate a piece of code that calls a function from another module.
Well, in AST land, what you would normally do is you'd have a concept called apply, right?
Which is like you say, I'm going to apply these arguments to this value.
And the arguments in general is like a list.
It's like an open ended list of like, I'm going to like apply X number of arguments to this.
So the first thing you'd sort of go to is you're like, okay, well, but I know this function takes two arguments.
So I'm going to manually write a little helper.
I'm going to write a little function that is going to take that function is going to take two arguments.
Behind the scenes, it's going to call apply with list, the unsafe thing, but it's only going to apply it with two values.
I know if I use this function to generate a call that I've guarded against something else.
I've guarded against arity issues, right?
So it's like, okay, we're one notch at imports.
Now we're guarding against arity, like calling a function with a number of values, right?
And then the let me maybe the last part is like, okay, but the next chunk is types.
Now this is kind of weird.
And like, so what I mean by that is like, okay, let's say we have a new function called add.
We've just invented this.
It's amazing.
Takes two numbers, smashes them together.
You get another number.
Yeah, super groundbreaking.
In this case, we're going to only operate on integers.
Now we could make our little helper function that like takes two expressions, like AST expressions and generates a new expression.
And it means that whenever you called that, you'd have to say like, okay, my first argument is an integer.
So you say like, this is an integer.
And then the second argument is like, okay, this is an integer.
And you like kind of wrap it before it goes in.
But there's another form of this.
Like you can just do that wrapping inside of the function.
So you have another helper that basically helper that's like my sweet, sweet add function takes two integers and returns an expression.
And then it just does the mapping.
And so there we've actually squeezed out another bit of safety because now we can call this my add function with two integers that we maybe know at build time.
And we get a expression that generates like what it would look like to call that function normally.
So now what I like to think about with this, I realize I've been talking a decent long time.
So hopefully I'm spinning a good yarn here.
But is that that form, that last form of you have a function add, give it two integers and it returns an expression that is that value.
That to me is like very close to being like writing the original code, which means it's very close to writing a string.
Like convenience wise.
It's also interestingly enough, the safest version of everything we've been talking about.
Now, the only downside is that in some cases when you're generating code, you don't always know all the values at build time.
Right. Like so like.
But what we've done and what I think is so cool is we've taken it from being anchored in the unsafe realm and trying to stretch and reach to the safety where it's like the farther we reach the safety, the more like painful it is.
Right. We've actually inverted it.
Now we say like by default, you are as safe as we can get in this sort of context and the most convenient.
And if you need to be more abstract, you can reach farther.
You can say like, I need to oh, I need to call this function with a variable number of arguments.
Like if I'm doing a builder pattern or something, something that's actually I mean, it's still relatively common, but it's not it's not the most common thing.
So there are ways to basically be like, OK, I can reach into my little tool bag and say, like, I want like the more abstract version because that's what I need right now.
But you're anchored in this like concrete type checked version of things or not not completely type checked.
There's another discussion, which I'm really curious to bring up of I remember sort of going crazy.
Again, this is like the, you know, Matt's, you know, nightmares of like you're trapped in a room like, you know, there's a bomb inside of you and you need to code generate it out.
I don't know. And and I kept thinking, like, could you could you make a library that has the thing of like if it type checks, it generates code that type checks.
And there was a form of this that I was really interested about.
And then and I kept trying to leave the door open to explore that.
And we maybe after we come to this area, we can go back to that of what it was. But there's a key insight of why that's not doable without some crazy type level stuff that probably doesn't that you that I think you would need or some other mechanism that someone much more clever than me would need to come up with.
Cool. I really want to get into that. But but yeah, to keep it to keep it at level one here.
I really like what you're saying about this philosophy of like, just as a broader concept in the Elm community, like, can we have our cake and eat it to as far as like, let's make it safer and easier.
Can we can we right it's like, the way I've been thinking about it is, can we leverage safety? And this is I think what what Elm does, it's just like, it's clear to me why it's not.
We sort of emphasize type safety in Elm. And I think some people may roll their eyes, which is fine. But I think the interesting insight for Elm and the thing that really struck me is like, let's leverage safety to make things more convenient.
Like, like, and that's not always possible. I don't think I mean, how could it be but like, for the cases where it is, it's kind of magical. It's kind of like, okay, you ended up with this thing that is safer, and also better to work with.
And sometimes also faster.
Yeah, there's a there's a lot there's a lot of things that sort of pile up there. And, and if you focus too much of like, okay, we got the type safety or like, or I'm just gonna have type safety, type safety, blah, blah, blah, blah, blah.
If you don't balance it out with that other thing of like, I am looking for opportunities to make this incredibly convenient, you'll you'll usually end up with something that's like, okay, this doesn't not like, what the heck am I doing?
You know, like, I'm drowning in types. I don't feel great.
There was, um, in my agile coaching days, I felt a similar tension, sometimes in a similar enthusiasm to like, dispel these myths that there's like a common perception that, you know, these these technical practices, like, you know, test driven development, or refactoring code and, you know, having having thoughtful, thoughtfully designed code that you iterate on and all of these things like slow you down.
You know, testing my code that's going to slow me down. Now I have to write a test, but it's feedback. It's shortening the feedback loop, and it both makes you go faster. And safety also gives you the confidence to just go and not look back not think about whether, whether you're doing something wrong, or whether, you know, some monster is going to trap you in a room.
You know, it's very freeing.
I, yeah, right. Yeah.
I can hear the flashbacks of your nightmares. Sorry to.
Oh, God.
So yeah, so so maybe to bring it around, like, just because I realized with some with the cogen stuff. It's maybe you're hearing it and depending on your experience, you may be very experienced with ASTs or not, maybe that term is actually kind of arcane and weird, which is fair.
It's very abstract.
Yeah, kind of what it comes down to practically is if you're like, you know, I really want to be able to generate some code some Elm code that, like, is basically just a view function, like it's some HTML calls.
What you would do is you would say Elm cogen install Elm HTML, that would give you this little helper module, you could then use that helper module with just like as you'd use any other kind of local module.
And you'd write code that looks a lot like a view function, something you're already doing. And the and then you hit run and you just have that, that would be yours.
And if you wanted to get crazier, you can push it farther. And there's some cool things you can do there too. But like, just to anchor the concrete part of this, it's like, it's so easy to be like, okay, here's this high level, you know, thing like, oh, they said it was so convenient and so safe.
And it's like, well, but but like, what were they actually doing? Right?
Yeah, it basically is trying to mirror what your generated code would look like as much as possible, right? Like you Elm dash code gen, install Elm slash time. And then in your generate dot Elm module that generates your Elm code gen code, you do import gen dot time.
And then you do gen dot time dot millis two posix or whatever function call and you millis two posix takes an int and gen dot time dot millis two posix takes an int. And so it looks like the code you you're actually generating.
That's right. Wait, it takes an intro or it takes an expression, generate expression.
It takes an int. But it correct me if I'm wrong, Matt, but it provides an escape hatch if you want to provide a general expression, but the more low hanging fruit way to consume it assumes it just takes a literal int that you already have.
Yeah, so the base assumption, the place you start with Elm code gen is the thing that is most convenient is that you probably and this gets into the use cases, but you probably know concretely the values you're putting into these functions at build time in the most common
to in the land. It's always like a gradient rate, like how far you go, right? But the most we want to say, like, okay, 85% of the time, you're going to be doing this. It's like, great, we're going to make that super convenient. Like, oh, you want to like you have an integer already almost by definition, you're
you're taking some source, some, you know, source of truth, right? The figma, the figma API, or like a graph qL, like schema, which you have these literal values or, you know, and we can just skip the abstract part, nearly completely, like, we are still generating an expression, right?
But, but you provide a concrete values, and you basically get kind of what you were expecting. And like, I think you all of a sudden don't need to think about stuff, which was causing us a decent amount of pain, which is like, imports, like, you use the age, use the time library, oh, it's important.
And you probably didn't even work like have to worry that that was like a thing. Hopefully, hopefully you're using this for like a few days, you're like, oh, wait, oh, interesting. Okay. Or like, sometimes, like a library doesn't even need to be important, if it's like list or something. And it knows, in most cases, there's still a few things I need to a few like little subtle bugs like that I need to smash, but like, it'll know like, oh, yeah, list dot map. Great.
I love that declarative quality. Does it also help you because so I've I've done a lot of string based code generation in JavaScript, and in Elm to generate Elm code. And so I'm very familiar with the pain points you're addressing, including the creating the correct imports, resolving type signatures correctly for types that you're sort of piecing together, it gets really tricky.
Also, like creating parameters that aren't going to have name collisions is, oh, is that also something that Elm code gen helps with like giving you valid parameter names that won't. So it's funny, like in focusing on the convenience, I skipped over like a decent amount that Elm code gen does.
Some of it. Yeah, maybe need some love, but it does track naming collisions. Not completely. So top level values aren't checked, you have to verify those. But those are usually pretty easy to verify, like if you have a top level value. And when you get into like building the body of something, it's actually tracking a set of like a scope of basically what values are in there.
And basically what values are in scope. And like, and add again, like, if it does detect a collision, it'll add in a little like underscore and then a bunch of numbers, I guess kind of like a little protection thing.
But it's not 100%. Like there are still some cases to catch. And like the challenging part with this, and where I was trying to get to is, like, if it doesn't have to protect a variable, it shouldn't. Because we want the code that's generated to be nice to look at.
And the primary use case could be generating examples. In fact, there's a discussion we should have about that because there's some interesting stuff there. But the code that's generated, I absolutely want it to be inspectable, like with the Elm GQL utility, which is coming up for release and, you know, however many periods of time soon.
We've been using it at vendor for the past year and a half, and it's been incredibly successful. So it's, it's more just like documentation needs to get up. But the idea there was that you'd write some graph QL, you'd write a query, you'd write a mutation, and you'd be able to look at the generated Elm code and looking at the generated Elm code would be like, should be like reading really well written Elm code, it should be very simple, very straightforward.
Here are the types that are coming in to run this query or mutation. Here are the types, here's the data you're going to get out. Like you don't worry about decoders, don't worry about whatever, you know that it matches the graph QL schema perfectly, right. And the generated code is part of the interface of that library.
It's like you're supposed to look at it, even to the point where what I was talking with was developing this with some of my co workers, like Duncan Malashok, and I were looking at this. And they made a suggestion at a certain point that even with the code being generated, it would be useful if we had examples of running that query,
especially if the inputs to that query were a little bit more involved. Sometimes they are, and inputs have turned out to be challenging with graph QL and Elm for various sort of subtle and dumb reasons.
And so I went down the whole rabbit hole of generating example code that actually turned out to be pretty cool. You know, you basically you say like, okay, we have this schema, we're going to say, well, you know, Dillon, you made this joke when we were getting coffee the other day, like computer, get me a, get me a, you know, a filter, a person filter.
And then, you know, you have it run out and try to construct a value. And what we found with construct and so with that example code, what was so nice, and I think is something that what I want as an area for Elm code gen that I'm currently exploring and we'll be talking about at Strangeloop of example code generation and how to leverage that into something as a developer resource, right?
So, you know, I think it's like, let's be fair, copying and pasting something, and maybe editing it, or like, tweaking it. I mean, you could be like, that's so dumb, you know, like, go to stack overflow and get a thing. It's like, yeah, but we know that this code is like, on some level, some level correct.
And so it's actually very useful to be able to copy and paste and remove some of this, you know, you're like, oh, I don't need that. I don't need that, whatever, whatever. And then you have a thing, it's like, okay, wonderful. Here we go. We actually used that to take it further, like, okay, well, we generate these example calls, basically to make using this stuff useful.
Now, some things happened. One, we, so small bit of background with GQL inputs cannot have unions, which means that are certain ways that you want to generate, you want to like provide a input, you want to say like, it's one of these values, and you kind of can't do that.
And it's very frustrating. And optional stuff is also weird, because it's actually sort of inherits the JavaScript sort of legacy or whatever language legacy of like, things by default are optional. And if it's optional, it's actually a number of states, it's actually either it's not there, or it's there, and it's null, or it's present, right.
And the tricky part about that is that present and null is actually a distinct state from absent, and some cases meaningful, but not always, usually not. So, so there are a few ways of handling this in Elm. And I don't really want to get into them because they're, you know,
yeah, that's a whole can of worms. And when that actually comes up, but I think it's interesting, using Elm code gen, I was staring at this thing. And I was looking at the example calls, and some of them were pretty big if the surface area was pretty big. But when you're saying that the example calls for the design decisions you've made for your library just becomes very obvious how they scale.
You're like, oh, if we handle optional arguments, using the phantom builder pattern or using the like a list of whatever is or like, there are no way of doing this, right. But you can immediately be like, well, this is what a huge query looks like. And this approach looks great when it's small, but oh, boy, there are some weird cases when it gets bigger. And
something that would be hard to explore when exploring API span hand, which is the default. So can you elaborate a little bit on how Elm code gen helps you generate examples. So essentially, it, we talked about you do Elm code gen install some package. So basic, and it gives you gen dot time dot whatever the time API is, or whichever package you're using. So you're, you're talking about essentially, using
fuzzers to call these types of API. Yeah. So the way the way we are, I originally did it was looking at the source of truth in this case, which was the GraphQL schema. And you're like, okay, I want to make this call to this query. This query takes this input. And what you do is you'd sort of loop over the schema and sort of say, can I make an exhaustive example? And it's like, okay, if this needs a string, I'm just going to put in a string that's called placeholder. If it's a
string that's called placeholder, if it needs an integer, great here you get five, you know, so the actual values, you just sort of make something up, just give me something I don't care. And you would kind of like loop over and, and generate calls. So yeah, so you're taking in this case, you were taking the GraphQL schema, and you were using and this is actually with the generated code, it actually exactly where we were. But this is a case where you might want a more abstract version of, of the
when we talk about the Elm cogen, like different flavors, right? So it's like the most convenient one is the very concrete one, you might make them use a more abstract version. And so like, in this case, because like the values you're creating, you may not know like, literally. So I'm not sure if that lands for everybody. But so but that's generally what you're doing. And what's interesting, though, and what I'm exploring more recently is actually being able to generate examples for any given
piece of Elm code using the like you can for a given Elm package, you can get the what's called the docs JSON, right, which decodes into a really nice type signature for every value in there, which basically the you can think of the docs dot JSON as when you go to the documentation on the Elm package site, and you see the listing of all of the types and the functions and the types that the functions take,
that's right, right. So that's the information that's there. Yeah, right. And it's just it's kind of it's just there. If you check out there's a project called project metadata utils, I believe, or something like that, which you ruin I know you're probably very familiar with. And so if you go to like the Elm type module there, you'll see a represent representation of Elm type system. It's just like super great. It's I was thinking about this where I'm like, oh, ASTs in general are like, kind of like the same thing.
And I kind of naturally have a large surface area. And I was like looking at that type specifically the type that represents type signatures, as this little this little baby AST, right? It's just like little tiny thing you just want to put in your pocket, right? It's just like, like, you're like, this is so nice. And like, so the thing I've been thinking about it, or I was I'm like, okay, well, we, you know, we did the graph QL example generation. And that was me just sort of figuring it out based on the schema. And because I don't want to get
farther away from it, I want to make the point, we also use that example generation on the graph QL side, we were doing a an upgrade to our back end, and we were changing graph QL libraries. And it was one of those things where it's like, okay, we were reasonably, we think it's reasonably good. But, but also, this is a global change to all of our graph QL handling on the back end, which is sort of nerve wracking, kind of no matter where you're at, right? So I was talking with Aaron White, and we were like, how can we get how can we
get more confidence around this thing? And I was like, well, have you considered ordering a glass of all your queries? Because basically, we had this example generation, it's like, well, we don't actually, I mean, valid query, completely valid queries, and mutations and everything. It's like, that's, that's good. But we're just testing that the graph QL libraries hooked up correctly. So we just checked, I just took all those example queries and mutations. And I just sent them to the server and said, like,
did you blow up? And we did that. And we caught like a few cases, some older parts of the database that had something weird with them. I forget what it was, but it was something. And of course, all the things that were returning was like, what are you talking about, dude? You know, the server was saying, but it's like, no, but the fact that you told me this means I know you're doing all right. Versus versus you just like screaming with your hair on fire, right? So
anyway, I wanted to get that point in because I thought it was I think it's really cool. Example generation turns out to be a whole I mean, this is program synthesis, right? It's like, there's a whole set of things you can do with this. So coming back to we went to the little side quest there. We had that cool coming back with that, those types from docs JSON, you have this beautiful little type that sort of represents elm types. I think I've been thinking about that you think about like, what are the sources of
information? And what are the inputs and the outputs for your different code gen projects? Like this is actually one that's very valuable and has some advantages over raw ISTs that are kind of huge, right? So it's like a few things one surface area way small, great. So it's very convenient to work on, you still have to do some recursive stuff. But this just the surface area of the recursive stuff you have to do probably not as big. Well, definitely not as big as a normal IST.
What do you mean?
Well, yeah, you have to like you have, like you'll to represent a function. It's like a lambda call. And then that's like a type and then another type, right? So whatever function you use to generate code using that elm type type, okay, is going to recurse on itself, but it's not going to be it's not going to be too crazy. So and separately, like there's this desire when you're doing code gen, possibly to know some types to operate on types.
Or at least like when you have access to it's like, okay, I know for sure that there is a value that has this type. And like, in fact, I know the whole bag of recipes to build these values from this module. Then you have everything you have everything you need to make a example cake, right?
Like you just you say you pick a starting point. And you say like, I'm gonna, and you could just pick every point and say like, I'm gonna start wherever and I want to end up, I want to end up with some HTML. Great. So I'm gonna start here. And of course, this thing may fail, right? Me and may not find a way to get to HTML. But it like, you can write an algorithm. And this algorithm is not very involved. 300 lines of code, maybe we say I'm going to start with this value. You're looking at your bag of recipes, you're gonna say like, okay,
okay, so you start with it, for instance, or something?
Yeah, you start with init. That's a, you know, that would be a natural place to start with some of these things. So like a UI button, right? So you say UI button in it, great. What that maybe, maybe your button module uses the builder pattern. So init does not create HTML, but it creates a button button type. So what you'd say is like, okay, my button takes a record, which has a string and a message, right? So like label and on click, cool, I'm going to make up a string. And I'm going to like, when I see something like message, and we have a little bit of like, just stuff, right?
in this like messages, messages, a special case. Then you say like, okay, great, I've created a
button. Now that I have a button, what can I do? And then you write a little bit of code to say,
like, well, are there any builders that could take a button and maybe a value and modify it?
It's like, okay, that's cool. That's cool. It's not that hard. A builder is just a function which
takes some stuff and then ultimately takes a thing and returns a thing, right? Great. Really easy to
detect when you have this. And then you're like, okay, well, do I have anything? Could I create
anything that takes a button and returns HTML? Oh, there's button.view. Great. I'll put that
in the end and then I'll claim success. I threaded the maze. Okay. So conceptually, I'm with you
about taking this Elm type, which represents a possible Elm type and that represents the thing
you see in the Elm package documentation that describes types and functions. And then taking
that and solving this puzzle to say, okay, I want to generate something of this type.
Here are all the things that are available. How do I create something like that? What I'm not
understanding is, is this something that you're just on your own going and taking some docs.json
and figuring that out? Or does Elm code gen have something to actually help you with this?
That's a wonderful question. So this is something that I'm using Elm code gen to help with. I'm
exploring this. Elm code gen does not do this right now. There might be a thing in the future
where it's like, maybe this lives in Elm code gen. This could be built entirely on top of Elm code
gen. I think there's nothing essential about Elm code gen. It doesn't need to work with internals
or anything. So this is more an exploration of what do I find exciting about code generation?
And there's this whole territory that sort of opens up when you have something that is convenient.
It's like, I'm not worrying about imports, not worrying about type inference. Oh, we kind of
forgot to mention Elm code gen does have type inference in it. Like figure out the type signature.
There are some limitations to it. It's not as smart as the Elm compiler, but that's fine because
the type inference there is a convenience. You can overwrite it if you know it. And it's actually
usually not that big of a deal to overwrite it. Let's dive into that a little bit. So
at the base level, if you do in your Elm code gen generate module where you're generating some code,
if you define a declaration and then in that declaration you just have a, you say, time zero
equals time dot millis two posix zero, then it knows the type of that, like the code you generated
when you did Elm code gen install Elm slash time knows all of the types for those little generated
stubs to help you build using that package. And therefore that declaration, when you generate it,
it's going to say time zero colon time dot posix, right? Cause it inferred that.
Yeah, that's right. Yeah. So like you'll, you'll add in little values, especially like calling
out to libraries and stuff. And ultimately when you render that declaration, it should have a
type signature, which is correct, except for a few cases. One, there are some nuances to building
type inference. And so there are some, there are some cases that I need to figure out. I think in
the general, like to be fair in the Elm GQL, the library I'm working on using the vendor, I work at
vendor, which uses, we have 600,000 lines of Elm code, FYI, 300,000 lines of them are generated.
But of the 1000 files, or I think it's 1200 or so that are generated, which to be fair,
we don't use all of them. And the ones we do use, you only ship values that are used,
which is important to consider when generating Elm code. You get, you know, you're not going to
bear the burden of a huge like asset size for the stuff that you generate and don't use. So
you just don't have to worry about that. But for those 1000, 1200 files that we generate,
the type signatures were correct. Like it didn't throw a type signature error for those. So that
feels pretty good. But there are cases where it gets confused. I think especially, I think the
case where there are going to be cases, which I'm not even sure that there's going to be a way around
it, which is when you're heavily using aliases to capture various states. Like it's just,
the question is, is just, it doesn't necessarily, like the Elm compiler knows all your aliases.
It knows it because it knows your entire project. We don't do, like we do very minimal amount of
alias tracking, right? So one of the projects where this shows up that's challenging is Elm
CSS because they use type aliases to mimic the constraints of CSS, which is challenging. So
there, like the thing you can do is you say like, okay, great. I'll just tell you what the type is.
I know what it is. It's actually not that involved. So you can say, you know, for this declaration,
the type is going to be this and then we'll be fine.
Okay. And you mentioned an escape patch where you can help it along if it does get confused.
What is that? That's the with type. Like, yeah. So any expression you can basically say,
ignore what you were doing. This is what you are. And then you're in charge. I mean,
you're still going to like, ultimately this code is compiled by Elm anyway. So great. Like you'll
have to figure it out. Do you have an option to not have a type annotation in case like even you
don't know how to, that's a good question. I think so if it errors, it's not going to have a type
annotation or at least it shouldn't. There are a few maybe soft errors where the inference is wrong
and it's giving the wrong annotation, but that's different. There's no way to sort of say like,
don't write any annotation as an explicit statement at the moment. So when the, when it can't infer
the type annotation, you won't have a type annotation. Yeah. When you come to find it,
infer the type, there won't be a type annotation, but the tool doesn't crash or the tool doesn't
say, sorry, I can't generate this code. That's right. We wanted it. Yeah. I think that's a right.
And this was something we were thinking about is like, I didn't want my dumb little type inference
code, which I'm like, look, this is really just like, if it's, if it works great. Otherwise,
yeah, there's ways around us. It's not that big of a deal, but like I'm trying to get to delightful,
but yeah, preventing you from generating your code is not delightful. So it will always generate
code unless your code doesn't compile. Right. So the original generation code doesn't compile.
So the, yeah. So you're delegating the type checking part to the compiler ultimately. Yeah.
I wish you have to, right? I mean, like, I mean, ultimately we're going to say like,
we're going to try our best, but if, yeah, if we can't figure it out, we're not going to,
we're not going to. And then there's, there was even a thing like, I know that there's a bug,
which Dillon ran into earlier today, which I'm so glad for, which I actually, in this line of
thinking of like, we want to generate your code. Like even if we sort of think that there's
something wrong, it's like, we're not the armators of truth right now. L, the L compilers, there is
a case in the type inference thing where it will do an infinite loop. Before it was an infinite
loop and it'd be like, you know, oh well. It's like, well, that's not great. So the way infinite
loops happen is when it's doing type inference, it's looking up like, okay, oh, this is a message.
What is a message? Oh, a message is an integer, right? So it does this sort of lookup of like,
here's this type variable. What do I know about this type variable? I have a bag of facts about
this type variable and I'm going to go and I'm going to try to figure it out. The infinite loop
happens when you have a type variable that loops around to itself. So it, it's a variable on it.
Like, oh, what is the message? Well, a message is a message.
Obviously, yeah. A message is a message is a message is a message. Like that was one of my
nightmares. So as if you weren't enough during this episode. Yeah. So, so basically what it
will do now is it's actually tracking what type variable names, what values it's already
done a lookup for. And if it runs into it again, it's going to give you an error message that says,
oh no, I'm not as smart as the UMP compiler, but we're good friends. Like, and I especially get
confused in these circumstances, but you know, try to be as like, Hey, this isn't that big of a deal.
So this is like, this is not an error. This is a, technically a warning, right? It's not going to
stop you. It's just your type signature for that declaration is not going to show up, which honestly
is probably fine. Yeah. And I have seen it also give, we're talking a lot about like cases that
it's not as smart as the Elm compiler, which is, you know, big shoes to fill, but, but also I've
seen it tell me about failures where it's like, Hey, this is supposed to be type HTML, but you
gave me a list. I think something's off. Right. So, but ultimately, so it gives you a warning if
it encounters something like that and tells you what it thinks the problem was. But then it says,
I'm, I'm not the arbiter of truth, like you said. So it still lets you generate the code,
but then you can always take that code that it generated and run the compiler on it to make sure
it that's, that's the ultimate source of truth. Right. Right. And then there's, yeah, there's a
natural like feedback loop there of like, Oh, you know, I ran Elm code gen on this code. And to be
very, it's only really giving you like meaningful type inference issue or warnings. If you're sort
of in the less concrete area and in the more abstract area, you're using expressions for
stuff. And so it'll say like, I tried to unify these two expressions and like one's an integer
and one is, who knows. So when that, I lost my train of thought, where were they going? I don't
know. And if you call, if you call gen.time.milis2possics, that's, that's kind of a cool case
because the type checking is built in where as a compile time guarantee, because it takes an int
and therefore if it compiles, it's, there's nothing to even check. It's not, it can't fail.
Yeah, that's right. Maybe this is a good time to get into that can of worms that you alluded to
earlier about why doesn't it go ahead and, and give like compile time guarantees that you will
generate something where the types line up. I love it. Okay. So I'm pretty curious about this.
It's, it's fun. Okay. So here, here's what we have. So normally in an AST, you have the AST itself is
one type, ignoring the different differences between declarations and expressions. We're just
going to focus on expression right now, which is the bulk of stuff. So there's one type and you have
an expression and like here's dealing with expressions kind of everywhere. Now, what you
could have potentially is because like in, you can have a type on top of expression, which is what
Elm Cogen already has. The inner expression is from Elm syntax. There's an outer expression,
which basically just has some metadata about stuff, right? So it's actually kind of grabbing
a list of imports that is in there, right? Just so it doesn't have to crawl the AST to get them.
And it has some other sort of type checking information on there that's not in the original
AST. So there's two levels of expressions, right? Now at top level, the one that's exposed that
you're most when you are working with Elm Cogen, you see expression. That's the one you're working
on is that top level interface. The thing that was making my brain go nuts is you could have
expression and then you have a type variable next to it, which is basically the thing you want to
generate. Now, the cool part about it is you don't actually need that thing. This is a phantom type.
So you could say, or float, you say Elm.float produces an expression of float. Hey, that's cool.
And you could definitely create a function that is like add float. Of course, it'd have to be float,
which is kind of irritating. Numbers are weird, but let's say there's not a difference between
the images and float. So you have add and it takes an expression float, another expression float,
and returns an expression float. And then if you didn't pass it, if you pass it like an expression
list of float or whatever, you'd get a compile time error. It'd say like, well, you know,
I, and it would be like, you know, in that developer catnip kind of a way, like kind of
mirroring the original error of like, you know, this function takes a float and float and you gave
me a float and you know, whatever. What the heck? So I was like, oh my God, this is nuts. This is so
cool. And so I was thinking about it and I was like, okay. And I was like, you know, dreaming
about this, having nightmares about it, whatever. You should stop sleeping, man.
Oh no, that's not the answer. Type safety for convenience. You go the other way. We got more
sleep. More nightmares.
So I was thinking about it and like, okay, well, where does this get weird? One part where it gets
weird is records. How do you make kind of records work? So records, you need a, you can no longer
have a record to create a record. You like to create a record in Lmcogen, you have a list of
fields, which is basically the name of the field and the value. That's a problem. It's not totally,
so like, again, we're a sliding scale of safety here. Like, and we're comfortable with that. We
are not like, so it's like, we kind of give up that like, oh, if it type checks, it generates
type checked code. We know that that's probably not doable without some shenanigans or maybe not
doable at all. I don't know. Why is that a problem? Is it because you're passing a list of?
Yeah, so it's the list part. So the list has to be all one value. So it's like, let's say I wanted
a record and it had two fields. One that is my integer and it's an expression integer. And another
one, which is my float, which is expression float. All of a sudden that doesn't unify.
I think we should clarify too. Like, I think if you're talking about the sort of generated
stubs that you have for like, for known APIs, for Lmcogen install some package.
That would.
You could do that because you could have manually put the phantom type in there. But if you're
saying, well, I just want to create my own record, it's not from any package or anything. It's just
arbitrary types. Now, like in order to get that phantom type to be a specific thing, you have to
pass it in something of that type, but you're not, you're passing it a list, not a record.
I think you could potentially do it with a builder pattern.
Oh yeah.
Yeah. So here's where.
But you wouldn't be able to get the field name into the type name.
So, and you couldn't, you couldn't. So the thing that I think you'd have to do even on the builder
pattern is I think you have to have a type hole, which is essentially where an escape hatch for
the types. So what this would mean, and this is why originally to add a field to a record,
it wasn't just a tuple of like a string and an expression. It was actually I have to say like
elm.field passes a string and an expression. The reason why is it would convert it to
a special expression, which is expression field. And then you could have a list of those.
And you could actually make it work out because you're basically saying, I'm going to throw away
the type information here. And you're going to let me do anything. I think you'd have to do the
same thing with a builder pattern.
Right. Because unless you generated a function for every possible field name, you couldn't get
the field names in that type variable.
That's right. And so it's like, okay, so I was like, okay, we have this, we have an approach.
We can do the field, we can have a little type hole, that's fine. It's not a big deal. It's like,
again, it should be more safe and potentially like kind of ergonomic or whatever. And then I realized
there was another thing that basically I was just like, well, dang, I don't think that we can do
this, which is in the most abstract case, you're not going to know what types you're generating.
Like if you want to generate a value of a type that you don't know of until generation time,
things get weird.
Do you have examples for that?
So like, let's say I want to generate like a, where it's like, I don't know the shape of the type
and the name of the type or anything. Like so even as it doesn't have to be very complicated,
it could be like generating a message type or something. I would all of a sudden have to sort of
start to, what I would have to do is I would have to start to mirror, I'd have to create a phantom
value, like, okay, here, like I'm generating a message like type. And on the generation code,
I'm going to create a type message equal, like type generated message equals generated message.
And I'm going to stub that in as the phantom thing for here so that it, but then
it just leads down this area where that level of abstraction, you have to start mirroring stuff
that you're going to be generating. And I think ultimately you can't because you don't know what
you're going to be generating until you generate it, which means you would have to actually maybe
generate those types first. And then it's like, where, like, if you're lost, I'm definitely lost.
Like if you're like having like, well, I'm not even following you. It's like, great,
then we're on the same page. And so then I'm like, oh, but you know, ultimately, and this is where I
would like, again, if we were to nail the top, top line thing, can we leverage safety for convenience?
And at this point, it started to become clear to me that that was a bridge too far and that there
may be options for some safety, but we would give up some expressiveness. And we would also
adopt a bucket of confusion. So like an example being like, if someone's coming to Elm, like,
you know, we should think of the questions that people generally ask when they come to Elm. One of
them, what is a decoder? What the heck? Like, what the heck? Second one, what is this little
message on HTML? Right? I mean, those are fair questions. Like, like those are, they're not dumb.
Like, they're just they're the first ones you should run into. And so it's like, okay, well,
do I want do I want this library to be where it's like, oh, this is for Elm. If you're familiar with
this in advance library, it is an advanced library, just because code generation, I think,
has to be kind of but like, if, if there's a chance that somebody that this could be their
first intro to Elm, and they would actually do something that they maybe in another language,
it was too complicated for if they do that, that's such a cool experience. That's so so so cool.
And I wasn't, I wasn't willing to have a type variable, screw that up. I'm in favor of type
variables in general, just so it's clear. But like, if I don't have to have one, I prefer not to.
Yeah, my first experience with Elm was basically building a linter. So playing with ASTs.
I think honestly, mine was parsers, right? Which are worse. Yeah. Yeah, but it was solving a big
problem. Like I was trying to parse guitar tablature back in the day. And this is actually in
lering Haskell. So it was actually if I would have known Elm at that point, or like the Elm
parsing library, if it would have existed, that would have been amazing. But this was that was
definitely where I was like, Oh, I'm hooked. This is amazing. I had this big bag of regular
expressions before, but this is like, so so good. So people can have deep technical experiences.
And if we can facilitate them, there's a there's a huge opportunity for people to be like, Oh,
that's that was so good. Why? It is that trade off we talked about a lot with like, when you add this
guarantee, like, if you do non empty string, is it really gonna like protect you that much? It's
like, pretty cumbersome. And it's adding like, or like the Elm HTML escape hatches for different
HTML node types. It's like, well, take that what if you what if you write an HTML node type that's
not a valid HTML nodes? What if you write empty string for the HTML node type? Well, okay, but
at least you know, if you use the core functions, if you use html.div html.span, you're going to be
fine. And if you use node, just don't pass it an empty string is probably okay. Yeah, I think
there's there's this thing of like, you think about the appetite people have for various things.
I'm not sure that people come into Elm with a huge appetite for safety.
I think they come into it for a big appetite for sanity, like clarity. I would say that's probably
the more the thing and then like you sort of think about it afterwards, like, oh, yeah, no
runtime errors. You don't think about no run runtime errors is like a thing that's desirable
until like you're in the pit of despair, like experiencing errors all the time. But like, it's
not the thing you strive for. Ultimately, like the thing is like you strive for I want to build this
beautiful thing or like, look at this cool code. I was able to make this and it's like, I'm not
able to make this and it I'm able to do what I could not do before. And it makes sense to me.
I didn't like go nuts. And in this case, ultimately, like you still have the Elm compiler to check for
things. So yeah, I think dropping the type variable to try and type check things makes a lot
of sense. Nice. Yeah, it was it was definitely painting me for a while. But like more and more
well, not more and more these days. I want to thread the needle on safety in service of
convenience. Right? Yeah, I like the balance you've struck. I wanted to touch on also like,
you you provide an API for for creating the generate programs that sort of run your generation
code. So like the default one, you just generate a list of modules. So you give it modules which
have declarations which have expressions in them and use all this Elm code gen goodness to
kind of safely generate files and it and it outputs files when you run Elm code gen run.
But then you've got some some ways to create a program that has flags. So like you've got
from JSON, which you give it a JSON decoder, and then you get access to those flags for
generating your program. So tell us a little bit about those kind of helpers. Sure.
Yeah, so in the Elm code gen, the CLI, the thing that runs stuff, the CLI, there's some JavaScript,
which is actually very simple. If you want to run an Elm app using node, if that interests you,
it's surprisingly simple. It's basically like, start this Elm application. If you have ports,
maybe you talk to it by ports. But so behind the scenes, Elm code gen is just, you know, starting
your own application and sort of it has two ports, which is basically I think there's a success and
a failure port probably could have been like one of them. But and it's expecting a certain shape of
JSON in both sides. And you're also able to pass data through the flags. So what I found in most
cases, the thing that you're the most common case that you're doing is taking some external source
of data and you want to do something with it. So you may like the most convenient way to do that
is like I'm going to make an API call and get some JSON and then I'm going to hand that JSON to
Elm code gen and it's going to do a thing. So I wanted to make that like just super, super convenient.
So like in the Elm code gen CLI, you can basically just say like Elm code gen space run space flags,
and then you hand it a JSON file and it will read that. And then you'll have to have a decoder on
your decoder side. But and then you can kind of do whatever. And then there is also some stuff
to be able to where it's like maybe the more advanced cases you kind of want this where it's
like maybe you want to be able to throw it. Well, you definitely want to be able to throw errors,
right? Like, like, well, you know, figma changed their API, like in this value is not whatever.
Or in some cases, I believe there's an affordance for it, like just talking to the terminal saying
like, here's a warning, here's whatever. So it was really just trying to make find what are those
really convenient things and, and just make them convenient. So the most most convenient one was
JSON data. If it's not, if it doesn't have the JSON suffix, it'll just pass it in as a text file.
So markdown, great, you'll get a markdown file, like, you can do whatever. I think there's probably
some additional stuff we can do there. Sources of truth that are interesting is like, like, I know
Ryan with elm land, which is a very cool projects, was looking at just your directory structure of
your page, and using that to drive your routes. I think that's that's super insightful, right?
That means the source of truth is the is the directory structure. And there's, there's no like,
a built in way to grab that in elm code gen. But it's really not hard to just wrap that in JSON
and shove it in the code gen. Right. So, so for instance, if you were if you wanted to have access
to the contents of an elm file, for instance, then you would pass that as a flag. And then you just
have one giant string. And if you wanted to have access to two elm files, then you would convert
them to some JSON and possibly Yeah, okay. I think there's probably a few different ways to slice it
like depending on it's hard to basically say, Oh, here's the way without seeing a specific project,
right? As far as what are the best ways to do that, it could be where it's all loaded in through
flags, maybe there's like a crazy async thing you have going where it's your you can still talk to
this thing via ports, right? It's the actor pattern. So you could, you could have a thing
where you're like feeding it files as you go, but you'd need to adjust the elm code gen utility. And
like, like right now that for elm GQL, to be clear, because elm code gen didn't have its own
way of running stuff, elm GQL, just because that JavaScript is not that hard to write,
just wrote its own JavaScript runner. Right. And so like, and can basically do whatever it has its
own CLI interface, like the intention with elm GQL is that it's its own thing. So I don't want like,
I don't want someone to start up elm GQL and have it say like, welcome to elm code gen,
would you like to call elm HTML and they're being like, what the heck are you talking about?
So like, I think what if people are looking to build tools on top of elm code gen, which,
by the way, if you are, I'm super excited to hear about it. And if you run into issues,
especially right, and if you don't run into issues even better, but probably what I'd recommend if
you're looking into it is like, can you even copy elm code gen CLI as a starting point and adjust it
as maybe you have different flags or like whatever, you know, but do what like, do what fits for your
projects and use elm code gen CLI as a reference for what, how to talk to stuff, but be pretty easy
to extend it. I'm actually really thinking about like, it would be cool if there was like an elm
pages harness. So like, you know, elm pages is for like building static sites and with v3 for
building sort of server rendered sites. But the heart of elm pages is this data source abstraction,
which is basically a way of like, asking node JS to do things for you. Make an HTTP request and
give me that data. Read this file, decode this file as JSON, whatever, right. So glob, glob a
list of file paths and get me that as the input, right. So it's communicating with node JS to like
do nodey things and then give you back elm data in a safe way, right. So, and then you can map that
into whatever. It would be really cool if you could like do, you know, data source HTTP dot get,
get some JSON data, decode that, generate some code with just elm code gen types, and then you're
up and running. And if that was like a little self encapsulated command line tool, that'd be so, so
useful. I would use that.
Yeah, I think I think there might be an opportunity for elm code gen basically as the library, right?
Like, right. And like, if you're already doing interesting things with, you know, data sources
and threading through stuff, and I mean, you're already generating files, right? Like, so this is
just like, exactly, like, you wouldn't even need to. Yeah, you just like, put elm code gen in
wherever you want to generate some stuff. I mean, ultimately, the vast majority of elm code gen is
just in elm. You can run it in an le, right? Like, I really like that you can run it. You can
transform the generated code to string, and therefore making it available to elm review,
which people can do cool things with it. Like, oh, yeah, there we go.
If your review is you're generating elm code before it's generated, is that useful? Probably not.
No, well, Martin Seward and even Dillon wrote some review rules where you did say do,
and in this ring, you have HTML code, and that transforms it to HTML or JSON decoders or
whatever. Okay.
So there's definitely some uses for it. I don't know if it's easier to write it using strings,
but maybe elm code gen would be perfect here. It would be very cool. Let's make elm pages code
gen review and then call it a day. Let's do it. Perfect.
Yeah, I actually used elm code gen like a while back for something when it was still elm prefab.
That was the original name. Yeah, I forgot about that.
I also own
It was pretty fabulous as advertised. The one thing that I ran into, which is not your fault,
but it's an unfortunate limitation, is that I accept all responsibility.
Well, yeah, if you want to, you can't put inline comments in expressions.
That's right.
Yeah, I was trying to use it for a couple of things where I wanted to do for elm HTTP fusion,
this app that Mario and I have collaborated on a bit, mostly Mario, but to kind of scaffold out
these decoders and stuff based on JSON responses. But the scaffolded code, sometimes you want to put
in these little comments in the scaffold code and that's a limitation.
Yeah, I definitely want it.
I'm guessing it's a limitation from elm syntax or something.
That's right. That's right. So you can have comments, top level comments.
You can have doc comments attached to declarations, right? So those are supported
and should be great. The challenge, this elm code gen is built on top of a library called elm syntax.
And what elm syntax does when it parses elm code, even though we're not necessarily parsing elm code
in elm code gen, except sometimes, is that it takes all the inline comments,
notes their source position, so their column and line, and then it drags them up to a
dictionary or not dictionary, a list at the top of the data structure. So it takes them out of the
AST. And generally, you know, I mean, there's a topic of discussion for like, do comments live in
ASTs? I think ultimately, it depends what your purpose of your AST is. Elm syntax, it's a
question that's interesting to ask, what is the purpose of elm syntax? I almost think that it
makes sense to actually keep the comments in the AST, even like in a way where it's not, doesn't
invalidate your thing. But that's the right now, I don't really want to get into like, well, it
would be a huge lift for elm code gen to figure out the math of where the comments go, because elm
code gen essentially doesn't care about source position, because it's not reading a file, we're
using like, we're so like all the source positions and elm code gen behind the scenes are like zero,
zero. So it'd be like, oh, no, now we have to like do this math. And if I if I screw up, then,
then, you know, your code code is going to be broken. So I was like, okay, well, I'm just going
to wait for some smart person to figure it out in elm syntax. And no, no, because I'm the one with
published rights on the package. I know. And I have no clue how to do it. Like, I've actually
looked at what different ASTs did in JavaScript. And it was, I found like 20, which was a
surprisingly large number. And all of them either ignored comments or put them in a list, just like
elm syntax does. And one of them put them in the AST. But then there's a question like, is this
is this AST node, the right one to attach the comment to? Do you want to put the comments before
or after? That's also...
That's what you would have is you'd have well, shooting from the hip here, like, and I don't
expect this to be fair, I have no intention of putting this on your plate. But like what I would
expect is you'd have two constructors, one for comment before one for comment after. And, and it
is sort of like a function in that it's sort of like has to be attached to another expression. You
can't have a comment just, you know, hanging out by itself. But...
Well, except if it's at top level.
Right. Well, track those separately, right? Maybe have a different flavor of top level comment.
But I know that's Elm format also has a lot of complexity around comments.
It's tricky. It's a tricky situation. And like, it's one of those things where it's like the use
case in this case is super clear. It's very clear in other cases that comments, it would be great to
just get rid of them. So like for compile, like if you ever look at the Elm compiler, it's like,
it makes sense that it's not tracked there. Like formatting, it does make sense. It's a whole thing.
And that's why I say when I like talk about ASTs being weird, there's also like varying levels of
information depending on what you're actually trying to do with a given AST. And if it's there,
it's great. If it's not, it's challenging. And it's hard to possibly add in stuff later.
Yeah. Yeah. But aside from that limitation, like it really seems like, you know, I mean,
you can run the type checking yourself anytime you want to after you generate something. And
otherwise, it's just a lovely tool. So thank you for introducing it to the community.
Yeah, that's wonderful. I'm very excited for it to be out.
I'm looking forward to what people make with it.
Yeah, exactly. Me too. Yeah, amazing stuff. What should people look at if they if they want to get started?
Yeah, so the main thing to check out is going to be the GitHub repo, which is just mdgriffith slash elmcogen.
There's also a post on the discourse, the Elm discourse of that sort of gives maybe a more involved motivation.
But I think most of that's captured in the readme. There should be like, just a few examples there to naturally like get started with.
I probably need to add more. There are guides on that repo that sort of lead you through the various like inception levels of code generation, starting out concrete and moving to more abstract.
I think to get started, I think it's really useful, or it's pretty fun to actually just mess around with an Ellie.
And just install it and just get a feel for it. I should probably have that set up on the readme as far as like to really get people started.
But and then once you kind of get a little bit of the flavor of it, it can be probably the next step is setting up a project and deciding what is your source of truth and what do you want to generate?
Those are the two big decisions, honestly.
Yeah, or npm install elm codegen review pages and coming soon.
Yeah. Yeah. Amazing. And we'll look out for your Strangeloop talk as well. Super excited.
Thank you. Yeah, it's gonna be good.
Amazing stuff. All right. Thank you so much for joining, Matt. It was a pleasure.
Yeah, thank you.
And Jeroen. Until next time.
Until next time.