Scaffolding Elm Code

We discuss the benefits of automating your Elm boilerplate, and design techniques for APIs that generate code.
March 28, 2023


Hello Jeroen.
Hello Dillon.
You know what goes really well with scaffolding?
Builders, yes.
And I think we're going to be talking about both of those things today.
Sounds good to me.
I feel like you're trying to make a pun but I don't get it.
I feel like I have to at least make some sort of attempt at a bad joke to start things off
otherwise people will be disappointed.
People will be disappointed either way I'm sure.
But you know, I have to do my best.
Yeah, I mean the joke that I had in mind was let's try to build a good episode or something.
Let's try to scaffold a good episode but we'll just go with yours and we will not edit either
of these out.
Oh no, these bad puns go straight to tape.
No second chances.
I mean we have removed so many good puns in our episodes.
People are going to think we only make bad puns.
But we just edit those out.
There are no good puns.
The worse a pun is the better it is.
That's the only way you have a true bad pun.
A true good pun is a true bad pun.
So scaffolding.
So we've talked about code generation.
We've talked about OMCODEgen which I think is a really lovely tool for scaffolding.
So let's first of all, let's get a definition like what is the difference between code generation
and scaffolding?
So do you want to take a stab at it?
So I would probably say scaffolding at least for computer science, right?
Scaffolding is generating code.
So it is code generation but it is meant for things that are temporary or to start something.
So you generate code that humans are afterwards going to maintain and change to their needs.
Whereas if when we talk about more regular code generation like, oh we need to generate
code based off this to do things like boilerplate linking, then that is not meant to be touched
by a human.
So scaffolding is more for, well, let's use this to start with and then we can change
So I would say that to me, code generation is a superset of scaffolding.
Often when we talk about code generation, as you say, we are, especially in the Elm
community, we're often referring to generating code that is not user maintained code.
You know, GraphQL, like take the GraphQL schema, generate some decoders and encoders or generate
an API or Elm SPA generates the wiring for your SPA application or whatever.
So those are forms of code generation.
But as you said, it's not meant to be touched by a human after it's generated.
Whereas scaffolding, and I really like this sort of term and metaphor, you know, you think
about the scaffolding around a building, it's like a temporary structure to help you create
something that gets removed once that thing has been created.
So you know, you scaffold something, you generate some code that a human can then maintain.
But once it's sort of built it up for you, you don't care about what was initially templated
out for you.
Yeah, the one thing that I would say is bad about this metaphor is that usually you end
up with a result that is pretty close to the scaffolding.
So for instance, if we try to scaffold a, an Elm project, while we generate a main,
we generate a init, update, view function, and that structure is still going to be there.
You will change the view function, you will change the update function and the init, which
is like still most of the application, but the core parts of the structure will still
be there.
Whereas we'll scaffolding, well, yeah, pretty much everything's going, is going away.
It's almost like, often it's like a pre assembled unit that you're probably not going to change
the sort of exterior pieces you're going to fit within it.
At least in our use cases, that's pretty common.
So I think like, so with Elm Pages v3, I've been really trying to, you know, you've talked
about this idea a lot that I think you credit Richard Feldman for, of take responsibility
for user experiences.
I really like this idea of like, looking at what is the user's workflow and their whole
experience in building something with your tool or package.
And with Elm Pages v3, I've been thinking about like, what is that end to end experience
if you're trying to add a new route, if you're trying to wire in a form, right?
And you know, I've been thinking about the form API a lot.
We did an episode talking about some of those ideas of all these API design techniques you
can use to make that easier phantom builder pattern.
I have some opinions about ways to reduce the amount of wiring you have to do and leveraging
web standards to not have to do extra work.
So the framework can manage these things for you and all these things.
For those who haven't listened to those episodes, you only have like six or eight episodes to
listen to.
Go ahead and come back to this episode afterwards.
That's right.
Pause now and go listen back.
So that's one dimension of taking responsibility for user experiences, making that flow simpler.
But then how do you create a new form?
How do you create a new route with a form?
So I've been really thinking about that.
And I think scaffolding is the answer.
And like Elm SPA, Ryan did a great job kind of creating some templating helpers that you
can customize the sort of these these add commands to add new types of pages in Elm
I've been very inspired by Rails and I've gone back and been watching DHH's sort of
early days, 17 years ago, demo of building a blog engine in 15 minutes.
And he actually really built it in more like three or four minutes if you watch the timestamps.
And then he's like adding comment sections and customizing things after that.
So that was kind of a groundbreaking idea at the time, I think, in the server world
to be able to just like up and running with a server framework rather than like creating
a bajillion config files and installing all these dependencies and setting all these things
You just like create a database, add some fields and run a scaffolding command and then
you have an app.
And then you customize the views from there.
I thought that was really cool.
And I think in some ways we've lost touch with that ethos of making it really easy to
get up and running with something, but it's still just as relevant today.
So I want to make scaffolding a thing again.
And I think in the Elm community in particular, because we have to be so explicit with things,
scaffolding is a really great opportunity because we like being explicit in Elm and
we don't like magic in Elm.
And in fact, there's not that much magic you can actually do in Elm.
So scaffolding is sort of the best of both worlds.
Like you don't have to handwrite all of this boilerplate, but you can sort of get something
up and running.
Now we still want to reduce the amount of boilerplate you need in the first place, but
it can still help you right then.
So clarify what you mean with scaffolding in this instance, because I'm familiar with
the concept of scaffolding a new project, for instance.
The scaffolding something else is a little bit more foreign to me.
I mean, I know what you have in mind, but can you maybe explain it?
Yeah, absolutely.
So for example, I've been, I actually created a video walking through this, but I'm trying
to get it down to 15 minutes like that original Rails demo of creating the same sort of full
stack blog engine with database persistence in Elm Pages v3.
And so do you try to impersonate DHH?
No, I don't.
Do the same accent, do the same.
It's tempting.
It is tempting.
It's really funny how when he's doing the demo, he like, he runs a command and then
like it works and then he goes, whoops, it worked.
He always says whoops whenever something works.
It's really funny.
So I'm definitely tempted to do that too.
So like, for example, like creating a new, you know, the route for creating a new blog
post entry that has like the slug and title and body.
I've created this sort of helper for running like Elm Pages run, which we talked about
Elm Pages scripts, refer back to that episode.
So you can run these scripts.
I created an API for helping you to sort of scaffold things using Elm Code Gen.
So you do Elm Pages run, add route, and then you give it the name of, you know, the new
blog post page or whatever.
And then you can give it a list of form fields, just like a Rails generator.
You can give it the like, you can give it title, colon, string.
You can do the same type of thing.
You can say, you know, you can give like a checkbox field, you can do colon to give the
types to these different form fields.
But so you can list out these different fields and it generates a route.
It has init and update and view and all of these things, which is like, you know, it's
a lot to write out a fresh page.
And then it generates the form using the Elm Pages form API.
And it wires up the rendering of the form and everything.
Now, again, I've gone to great lengths to try to make the wiring very minimal for the
Elm Pages form API.
But I think that that's like, I think we have to attack it from both sides.
We have to make the boilerplate minimal, but then we have to help you build that minimal
boilerplate to give you the full productivity experience to take responsibility for user
I wanted to be able to have that DHH.
Like I want to do that DHH level of productivity.
Like I want people and, you know, Rails was sort of a groundbreaking approach to full
stack when it was introduced.
Now things are moving in this direction where full stack frameworks are often sort of more
tied to the front end.
So you can render full stack things, pull in database data, and then hydrate that into
your preferred front end framework.
But we need to like get back to the roots of Rails productivity to make something that
people really love and can be very productive in.
Now Rails has a lot of magic.
Elm has a lot of boilerplate.
And between like meta frameworks that take away some boilerplate and scaffolding commands
that help you assemble the minimal amount of explicit Elm code to reduce that boilerplate,
I think there's something really compelling there.
And I think like in Elm, we like writing decoders if we need a decoder.
We don't want to be implicit and magical.
So we should use scaffolding commands for why not have a scaffolding command for generating
JSON decoders, or this is for generating forms.
So whenever you say we need to scaffold something, you run a script through Elm pages or something
custom that you built or just run Elm code gen with a predefined code generation script.
And then you generate a new file.
Is that the idea?
You generate a new file that represents a route, a new file that contains a decoder
and encoder, stuff like that.
So one thing that you could also do is just be a regular person.
And if there's already a route, just copy paste it and adapt it.
Why not do that instead of scaffolding something from scratch?
When should you reach out for scaffolding instead of copy pasting, which could be closer
to what you want anyway, or also in a way pretty different, but who knows?
It's a great question.
I think to me, it's about we have the technology, we have the ability to like automate things.
And so, but if we don't create nice interfaces and create the right tools to automate these
types of tasks, then we are going to just find it easier to copy paste things and we're
going to do that.
And, you know, that's fine, but exactly like it's just, if we're copy pasting, that's just
a sign that we haven't done a good enough job making it easy to do it in a better way.
And so what is that better way?
What are those tools for that?
So like for the, so like in the ElmPages V3 API, I have a couple of scaffold modules,
scaffold.form and scaffold.route.
And so this is one of the things that I'm experimenting with.
I think that Elm CodeGen is a very nice tool for scaffolding out code, but it's somewhat
low level.
So it generates helpers for you for, if you want to run, you can do
and then you can pass in the corresponding expressions or it doesn't generate all of
the high level types for you.
A lot of the types end up being Elm.expression because it takes some argument, but that argument
could be coming from like a variable you have in scope and it, and it helps you sort of
manage what variables you have in scope by if you, if you define a let using Elm CodeGen,
then it, it lets you name a variable that you have in scope.
So you have an actual variable, but it represents an Elm expression, which is the variable in
the code you're going to generate.
It's a little bit, hurts your brain a little bit, right?
But it does a good job sort of mirroring the code it's going to end up looking like, but
it only does that for like at a lower level.
But if you have like a higher level API, I think there's an opportunity to create abstractions
that mirror those APIs.
Like if you have a builder pattern, so like in Elm pages v3, there's, there's a sort of
a route builder for defining your route.
Is it a server rendered route?
Is it a pre-rendered route?
Does it have local state?
Does it have shared state?
Does it have no state?
So that's a, that's a builder that you can sort of tack on these options.
And if you, you know, if you do with no state, then you need to give it a view function.
You need to define a view function.
If you do build with local state, you're going to, in addition to the view, need an update
and it subscriptions message and model, right?
So that is too like Elm code gen isn't going to help you with knowing that you need to
define an update function in the one case and only a view function in the other case.
And you're going to need to define these types.
There's no way for it to know that.
So we can help it out.
So I, I basically mirrored that API and we'll, we'll link to these modules in the, in the
V3 beta docs of Elm pages, but I tried to, to give some high level helpers.
Now like one of the keys here, you know, when, when you are doing the builder pattern, you
have this sort of builder type that at the end you turn into whatever thing you want,
like an Elm code gen expression or whatever.
So you need a level of safety that you're not just sort of constantly taking an expression
and applying functions to that expression and things like that.
Because then you just have like this amorphous expression and you can do a bajillion things
to it.
And it's not really like clear how to scaffold this code.
So this API has like a, like a builder for the, for the scaffolding type for scaffolding
a route.
So it's sort of like a, for the, for the route scaffolding, it's a builder for the route
So you apply these sort of sets of options for generating a server rendered route with
local state.
So now you're tacking on these options to give all of the function definitions that
you need for those options in the builder pattern.
So I think essentially like my assertion here is that I think it's a good idea for packages
to create higher level APIs that mirror their APIs for scaffolding.
So like for Elm review, if you're scaffolding out a new Elm review rule, which there is
a feature for already, but yeah, right.
And we should talk about that.
So now I will say when I'm working on Elm review rules, I do often go to the docs and
copy paste an example of a visitor, for example.
Those examples are just awesome.
So go ahead.
And I mean, and there is definitely an art to having examples that are copy pastable
because things are defined in a way that not only compile, but you know, maybe you take
in certain things as parameters so that it's like a nice sort of self-contained example
that you can probably copy paste into your own code.
So there's definitely an art to that.
And you've done a good job of that.
Now the question is similar to what we were talking about before with like copy pasting
a new route module, can we beat the copy paste experience?
Because if we can't, then why waste our time?
Because we can already copy paste anything and that's what people are going to do unless
we, you know, like that sets the bar, unless we go past that, people are just going to
copy paste.
So there's one reason why every time I work on a new rule, I start from scratch.
So I do use the Elm review new dash rule command, which scaffolds a new file and it scaffolds
a new test file.
And reason why it starts from scratch is because I want to do things in a TDD style.
And if I copy paste some code, then I don't have the test for them because the examples
are good, but there are no tests for that specific example, which makes a lot of sense.
I think if I wanted to copy paste something, then I would also have to copy paste a test,
which is usually not very applicable.
So therefore I start from scratch anyway.
That said, there are multiple variants of Elm review rules.
So there's module rules, which are very simple or simpler, and there are projects rules,
which are a lot more complex.
And I do think that I should probably have a scaffold for each of those because every
time I work on a new one and I need to use a particular rule, I need to migrate the module
rule to a project rule.
And that's a lot of work.
So yeah, I should probably scaffold a variance of that for the more complex approach.
But again, I would still want to start from scratch because of TDD.
So like, I'm totally with you on the TDD thing.
I mean, I just brought up TDD because I know you wouldn't be able to contradict me after
That's right.
That's right.
So here's the way I would think about this is, and this has been my experience, like
copy pasting things from Elm review is I'm not copy pasting for the behavior, I'm copy
pasting for the wiring and boilerplate.
So it's like, I need to add some sort of import visitor.
And it's like, now I'm in my workflow, I'm going to have a failing test that says, whatever,
this thing that is a contextual thing from the imports, whatever needs to add an import
expression or it need if it doesn't already exist, I have a failing test that shows that,
okay, now I have a failing test.
Now what do I do?
I need to add an import visitor.
Now I need to copy paste something because I just like the functions have a certain set
of types, it gets wired in in a certain place.
I'm just going to go copy paste something.
I'm going to go find the with import visitor docs and Elm review, and I'm going to copy
paste that.
And I think that makes sense.
I do that as well, quite a lot, especially for the project rules that I mentioned before.
So I really like no op type boilerplate for that reason, because you can do it effectively
as like an atomic step that's not changing your behavior.
So it fits in really nicely with TDD where effectively it's just like with TDD as much
as possible, you want to work in atomic steps.
So if you have a green test and you want to refactor something, right?
Well, if you have an automated refactoring, it just, everything is fixed.
If you have a manual refactoring, that's going to at some stage have things in a broken state,
even though conceptually it just belongs as one atomic thing.
So automation can get us closer to this atomic set of steps.
And I think it's the same for like scaffolding, basic wiring.
And I think it's, as you say, for that reason, it's best done as scaffolding out no op type
So, so I do absolutely get the no op operations that you mentioned, but for me, scaffolding
is usually always either a new file, if we talk about code generation, or if we talk
about code snippets in your editor, for instance, then it's pasting or generating a short piece
of code.
So the, the code snippet could be used to do what you just said.
But if you, if you talk about code generation or generate files with code gen or something
like that, then you can't just, I don't see how you integrate it into an existing file
Or at least I don't have the tools for that.
It's a great, it's a great point.
And I think this is something worth exploring.
I think that, you know, maybe, maybe we need some conventions and tools for these types
of things.
So for example, like I can imagine Jasper has this Elm pair tool, which we should probably
dig into at some point, but we'll link to that in the show notes.
But it's kind of built on, on the idea of this workflow, similar to these sort of Elm
format workflows, where it automatically fixes things to do what it thinks you expect by
changing a colon to an equals to make the syntax, right, things like that.
It will, you know, fix your import aliases and things like that based on you just editing
the code, right?
And you could also use GitHub copilot or other GPT AI things in the future, which could do
the same thing.
Sure, sure.
As always, like with some amount of trust and distrust of the tool of the general code.
And you also mentioned a few times on the podcast, and maybe we'll get there at some
point having Elm review be able to pretty much act like a language server.
Or have some kind of tool that allows you to write custom actions.
Right, right.
And, and now the thing is, like, we already have some, some simpler versions of that.
So it's not like a language server that's nicely integrated into editors to expose intentions
under your cursor.
But you know, my Elm review HTML to Elm package, you can do debug.todo with some HTML with
a sort of magic debug.todo call.
And it takes that to convert it and to scaffold out some HTML for you.
And it sort of intelligent scaffolding because it's aware of context.
And so we can use similar patterns.
So you could imagine a similar pattern.
You know, and Martin Stewart has, like a similar sort of unpublished tool, but he's worked
on this Elm review to do it for me.
Tool that sort of helps you scaffold out decoders and things like that based on these magic
debug.todo calls.
Alex Corbin has an Elm review rule for JSON to Elm.
Yes, right.
Martin Stewart did some things similar to that and others, but yeah, not published.
Yeah, I originally got that idea of that pattern from Martin Stewart and used that idea to
for Elm review HTML to Elm.
And like, so that's a, like, that's kind of a cool pattern.
And you could even imagine having, for example, like, to be honest, I haven't integrated a
workflow like this myself, but I think it could be cool.
What if we have a bunch of these sort of scaffolding Elm review rules?
You could even have like a special review scaffolding folder, right?
So a normal Elm review folder by convention is in./.review.
Well you could have like a separate review config with your scaffolding ones for scaffolding
out decoders, HTML, helping you scaffold out Elm review rule helpers, helping you scaffold
out Elm pages forms, you know, the sky's the limit.
And then have that running in the background in watch mode in auto fix mode.
That could be really cool, right?
Yeah, or with a nice integration with the editor, which is currently a little bit lacking,
but yeah.
And I think that this is actually a very nice progression to, you know, sort of do the 8020
version of it where you don't have to build a fancy thing that gives you options from
under the cursor and you just use debug.todo to have valid syntax, but give a special keyword
within the string in the debug.todo that Elm review interprets.
I mean, you would probably have a code snippet that creates a debug.todo with the right thing,
and then you place part of it and then you would have Elm review.
That's an amazing idea too.
So like, I think it would be really cool to have like, let's say like import visitor,
you know, what if, and see, here's the thing is like, like Elm review, the actual Elm review
package could even ship some of these either scaffolding helpers for like Elm code gen
stuff to help with this or an Elm review rule.
Now that's meta if Elm review ships Elm review rules to help you generate Elm review rules.
You mean instead of having Elm review rules for reviewing Elm review rules, which is,
is probably going to happen at some point, to be honest.
The snake eating its tail.
I love it.
I love it.
We can add steps to it if you want.
Who needs copilot?
We're going to have Elm review rules to review scaffolding tools for Elm review.
That generates Elm review rules to, and then this circle is.
I did use Elm review HTML to Elm to build Elm review HTML to Elm.
The website version.
There's like a site that you can paste the code into and I definitely used it to build
So that's good.
Gotta love a good bootstrapping story, but, um, and, and I mean, I made Elm review and
since then I have not made much with it.
No, I do use it at work, but yeah, maybe one day I will start custom projects.
Probably an Elm review website.
One day.
And a scaffolding helper.
I think it would be great.
Like, so I think I like this pattern.
I think, I think I'm a fan of this idea that we've just developed here.
You heard it here, folks.
And then it never happened.
So you see some value and I mean, I, me too, but you see some value in having scaffolding
tools to help you with tasks that you do a lot.
Small tasks with things like HTML to Elm or bootstrapping tasks, like a new route or a
new rule, that kind of thing.
How do you identify what you need or when a scaffolding would be a good tool and where
it would be worth the investment mostly?
Because I mean, copy pasting is still an option or rewriting everything from scratch is still
an option.
And for the fair short run, there's always going to be faster.
So how do you identify those?
And we can talk later about how do we know what to generate?
I think it's, I think it's kind of similar to Jeroen's hierarchy of constraints where,
so like, if you can make impossible states impossible, then you should do that.
Don't write an Elm review rule to give guarantees that the compiler, that you can use the compiler
to directly give you that.
Like yes, Elm review rules are great, but they're there when you need them because the
compiler can't give you that.
So it should, you should go up, up the pyramid, start with the most robust way of doing it
and the first class approach.
So similar with sort of scaffolding, if you can eliminate boilerplate by not needing it
at all, because you have a more declarative API, you have a clever trick that reduces
the amount of boilerplate you need without introducing magic or complexity, then you
should absolutely do that.
And again, I went to great lengths to try to do that with my form API design.
So attack it from both sides.
So that's number one.
But then once you do notice that, okay, I've reduced the amount of boilerplate I need to
set this up, but it's still, I think that there are a couple of things to consider in
your workflow when you notice something taking time, but also getting in the way of your
thought process because it, it's a context shift, right?
So it's not just the amount of time something takes, but it's the amount of context shifting
it requires.
And because of that, sometimes these sort of workflow automations can be greater than
the sum of their parts where having an automated refactoring, maybe it saves you two seconds,
but maybe it makes you more likely to do that refactoring.
And maybe it allows you to stay focused on the problem you're solving and let refactoring
be something that takes no, no brain space.
So that's a win, even if it doesn't save you time.
So I think those are a couple of things to consider.
There's also just sharing with others in the sense that for instance, you might know how
to create a new L module and to wire all the updates functions together, the view functions,
the subscriptions and all that.
And maybe it takes very little amount of time for you to create all those and to wire everything
But for a beginner, that will take a lot more time.
So if it's for you, it's already automatic and you know how you could do, and you know
how you could automate that, then that could be worthwhile for a beginner.
Or maybe not this example, but yeah.
You can use it to model best practices.
You know, the things that get copy pasted might not end up being the best examples of
things to get copy pasted.
So you can sort of model the ideal way to write something in sort of scaffolding helpers.
That's actually something that I've found that, that's actually something that I'm finding
to be a lot more of a problem than I expected is that if you leave bad code around, it gets
copy pasted a lot.
It's always the bad code that gets copy pasted, but that might be some bias.
I hope it's a bias.
No, it is exactly.
And which is something to say about code debts, like fix it now or it will multiply.
Sandy Metz talks about this a lot in refactoring.
She has some really great books on refactoring in Ruby, but she talks about a lot of just
sort of general best practices and refactoring techniques.
And she talks about sort of, I can't remember the word she used, but something like replicability
or like somehow code wants to be duplicated.
So there's a cost to having code that doesn't represent the way you want to do something
in your code base.
That said, I mean, you can write tools to say, well, this is the way things should be.
But if you're starting with a new approach and you're still exploring and like, well,
is this the right approach?
Maybe we'll try it out and come back to it later.
If you don't know what the right approach is yet, then you can't really make tools for
So, but for things that you do over and over again and always in the same manner, yeah,
make tools, make your life easier.
Now, just as you want to keep in mind, like what direction you want to guide the code
with your scaffolding and your automation tools and how that shapes the code, you also
want to make sure that you don't make it too easy to generate things that you don't necessarily
want or to duplicate things rather than reusing shared things.
This is another consideration.
Are you thinking of, for instance, when someone creates a module and they add in it, update
view and in the end they only need the view?
Is that kind of thing?
Yeah, that would be one example.
Or maybe it's so easy to scaffold out a new decoder that you don't go to see if there's
an opportunity to reuse something that already exists somewhere else because it's so easy
to just build up the decoder using these automation helpers you have.
But yeah, I think, you know, with Elm, we like being explicit.
We like to have all of the information of how to do something in code, not defined through
magic, not implied through some type somewhere.
And so I think scaffolding is like a really good technique to bridge that gap.
So like one of the things to think about also is like, what are the inputs to your scaffolding?
So like, you know, we talked about using these sort of debug.todo comments that have some
sort of something in the debug.todo string that tells it what to do, what to replace
that with.
That's one pattern.
Having sort of an inline thing in the code telling you that you want to do something
with that.
You can use a CLI to scaffold out new things.
You can pull in data from different sources.
You can pull in, I mean, I think it would be really cool to have a JSON decoder scaffolding
helpers that take a URL or an HTTP request or something and scaffold that for you.
So I think it's worth thinking about what are the inputs to and how can we really automate
the process to make it really seamless to not have to think about boilerplate in our
own code.
At work we have a few code generation tools or scaffolding tools, for instance, to create
routes and then we have a separate code generation script to combine them in an Elm SBA like
So there's a lot of scaffolding and code generation going around that.
One problem is like, usually people don't know that these things, these scaffolding
tools exist.
So they're in the readme, but no one reads the readme because I mean, it says readme.
It doesn't say ignore me.
That could be fun file to have ignore
People might actually read that one.
I wouldn't read it.
I like it.
So yeah, we have the problem, but I mean, the tool is there sometimes.
It's just not going to be used.
That's all.
So if you do have like, if you make a package with those scaffolding tools integrated into
the package, then I would really recommend like add a nice section in your readme about
like, Hey, you should use these things.
Then there's the same question I was wondering about a few years ago is like, well, if you
have a package, like let's say Elm UI, should you have Elm review rules that come with it?
And I would, today I would probably say no, because then you're adding additional dependencies
and you should probably have a separate package with the Elm review rules.
And I think I'm thinking maybe you should have that too for the scaffolding.
I mean, I think the challenge there is keeping them in sync between versions, because if
it's in the same package, so like if you have a breaking change, how do you, how do you
make sure that the scaffolding tools are being run against the same target version?
That's one challenge that you introduce by having them be separate.
But now, I mean, the problem with dependencies that you now depend on Elm Code Gen, which
has maybe other dependencies, and now you risk of having like some major dependency
version mismatches, which I always find very scary, at least.
So just to elaborate, for people who might not know, in Elm, pulling in dependencies
to a package doesn't eagerly pull in those dependencies to your actual code.
So it's not like JavaScript that it is going to bloat your bundle size just because you
depend on an extra dependency, or that it's going to install it for every single time
you npm install your node modules is going to have a duplicate version of that code.
And it's going to blow up your file size on your system.
There's a single shared place that it caches on your system.
So it doesn't really cause problems for that.
Elm has live code inclusion, or dead code elimination.
So it doesn't really cause issues for that if you don't call a function in your production
code that uses the scaffolding thing, it doesn't pull in those dependencies.
But if you're upgrading to a new version of some package, and that dependency creates
a conflict between that upgrade, that's where it causes headaches.
Yeah, it's mostly those Elm Community extra packages that scare me a lot.
Which often it's nice to just like inline those because it's not worth making it difficult
for people to...
So again, to clarify, if there's a new major version of, let's say Elm Community list extra,
so there's v9, and then an old package or another package depends on v8, then you can't
install those two at the same time, because of how Elm works, which makes things simpler.
But these kinds of problems do arise.
Yeah, we need some sort of like scaffolding tool to inline extra helper functions.
No, I mean, it's, it would be nice.
It's definitely not ideal to like copy paste these helpers into packages to reduce these
But if it's like one function from list extra or two functions from list extra, it's not
worth having users have that dependency dance if something has a breaking change.
I mean, from what I understand, NPM has this ability to install multiple versions of a
package, right?
But that's also the reason why your node modules are so huge, because it installs plenty of
those versions, because you can depend on packages so easily.
And it can cause weird unexpected behavior too, right?
So it's like, it definitely simplifies like reasoning about your code to be like, this
is the version of this dependency that that all of my code uses.
So yeah, Elm has this little limit limitation, but yeah, we're looking at the node modules.
I'm like, yeah, this is pretty good.
It's a pretty sane limitation.
It's a sane constraint.
Elm is good at this.
You mentioned inputs for the scaffolding, but you were talking about how do you integrate
these scaffoldings?
How do you use them?
What method?
What about the inputs to the scaffolding tool?
Like should you, for instance, should you have options to your scaffolding scripts?
Let's say we want to make a new module or let's make a new Elm project.
Should we have an option to say, well, I want a browser sandbox or a browser elements or
a browser application and so on and so on.
How custom do you think these should be?
I think first of all, it has to beat copy pasting as a user experience.
So that's one way to beat copy pasting as a user experience.
Now if you make the running the scaffolding tool overly complex, it's not going to beat
copy paste, but fair.
If you can have a nice CLI.
Now Elm pages scripts have the option to do a CLI options parsing that's in pure Elm using
my Elm CLI options parser package.
So you can use that to add configuration to a scaffolding script.
So I think that's like a pretty nice thing to do.
If there's like one little flag you want to add to configure one, one change, you should
do that.
Do you think it's nice for a scaffolding tool to have a few prompt questions or should everything
be configurable through the CLI flags?
I mean, you could do both.
Like if you provide something with the CLI flag that you don't have to ask the question,
but they're not necessarily as discoverable or have the same amount of explanation in
the prompt.
I quite like prompts, but it does make things a bit slower.
It's definitely a trade off between ease of discovery versus ease of use once you've gotten
comfortable with it.
So I mean, sometimes tools will sort of have a required flag, but if you don't pass in
that flag to the CLI, it will prompt you for it.
That can be a nice way to do it.
Sort of a best of both worlds approach.
It strikes me too that like, so with these magic debug.todo comments that an Elm review
rule can find with the special string in there, you could, you know, I think it's good to
have conventions for these types of things.
So they're discoverable.
You could even imagine having like, I don't know, let's say that all of your magic debug.todo
comments start with triple exclamation point.
So you have three exclamation marks and then maybe the Elm review rule watcher that's running
in the background says like, instead of giving a fix, if you just have three exclamation
points without anything after it, maybe it says, Hey, here are the ways to fix that.
Here are the options you can pass me.
So you could have a discoverability experience through that.
So like, I think there's a UX opportunity here.
It could even be an interesting thing to have like a shared package to crystallize some
of these conventions, because I think like conventions are important for user experience
because otherwise like people aren't going to go to the readme to look it up.
You have to like make it more within reach.
We just have to understand that about people that they're probably not going to read the
readme, but if we make it easy for them to guide them in the right direction, when they
try to do something, they might actually use it instead of copy paste.
That's actually something that is, that's actually something that I find it to be a
bit of a shame that it's not very discoverable.
So for instance, if we're pairing together and I'm running a function or actually you
just look at my code, because I've sent you a pull request and you see a function that
I've used and you've never seen before, or you go to the documentation and you see, Oh,
well, list.partition is a thing.
Oh, that could be pretty useful.
I will use that next time.
But you can see that because it's in the code and it's committed and all that.
But all the things that I have done that you have not seen because it's not committed,
like all the keyboard shortcuts that I use, all the editor integration that I use, all
the scaffolding tools that I use, you don't see them.
That's actually like one of the reason why people say, Oh, you should pair together because
that's when you learn so many new things.
Every time I pair, I learn a new thing with someone.
A hundred percent.
Or teach someone something new.
It goes both ways.
I agree.
And workflow, like, I mean, one thing I really learned from a lot of sort of people in these
software craftsmanship circles is like, workflow and automation are not just like a vanity
sort of cool way to customize your system.
It's not like, Whoa, look at this cool theme I have and look at this cool shortcut I have
for this and this cool automation.
It's like, no, actually, like automating things is a really important part of like building
great software.
In my view of building great software, like this is a key way to get better at doing that.
So I think it's be taken seriously.
I mean, it would be hard to argue that using notepad is the way to write applications today
when the only shortcuts you have is control C and control V.
So yeah, you know, all the things that a editor gives you, even a simple one, well, they're
They're really useful.
And again, because it helps you get to those atomic steps of changing things.
Now, I'm not going to say that somebody can't create great software without these things.
It's just like they're missing out on a superpower.
You can also like another superpower is being really good at modeling systems and knowing
when to say no to certain features and writing tests first and things that you can do without
automating these steps.
So I'm not saying that that's the only part of building great software, but I think it's
like to me, it's an important piece of the puzzle.
It's an important tool in the toolkit.
So going back to scaffolding and all those things, what would you write in the scaffolding
So again, let's take the example of you scaffold a new application with a main and init update
view browser data application, whatever.
How much comments code would you integrate?
So for instance, one thing that I might be wondering is like, well, if I want this code
to be for anyone who's new to Elm, then I might add a few comments that says, well,
oh, this is main, this is how your application starts and this is how it works.
And then you have a comment around init that says, well, this is how we initialize your
application and one for model.
This is all the data that your application can hold, et cetera, et cetera.
So is that something that you think we should do?
And also like maybe also steps for, oh, well we have now scaffolded this thing and this
is where you should change your code.
Like here's where you should add a new kind of message variants or this is where you shouldn't
handle this new updates, this new message.
How much should you help someone?
And I'm guessing it's like, if it's for you, you don't care because you know what you need
to do.
I mean, you've been able to automate this, right?
But I'm guessing if you're in a team and you often have new developers joining your team,
then maybe it's worth adding all these comments, but then you probably also want to strip them
I'm a little bit torn.
So back to this sort of idea that Sandy Metz has that code tends to be duplicated or replicated.
I think the term she uses is, is that good code is exemplary, exemplary, exemplary, exemplary,
We'll see how our transcripts do on that.
Maybe it'll just strip it down to one word.
We'll see.
Yeah, I think it's you, you want, like, if you don't want to have comments in your code
explaining this goes here, this is where you add things.
I personally would tend to avoid including that in the scaffolding as well.
I would, I would want to add that more as like output from running the scaffolding command
and it says, great, I added this thing.
Here are your next steps.
Now what I would want to do.
So for example, let's say we're scaffolding out in a new route module for SBA, Elm pages,
your custom homegrown SBA helper, whatever.
So personally, I tend, I actually tend to find it more useful to like, have an init
function defined, even if you don't use it initially.
I just find that easier.
So then you don't need a comment to say like, comment, if you want to add init and messages,
you can change these things.
Just, just have, you know, type message equals no op or whatever, you know, but I was, I
was thinking of a comment next to the message type, but always including the type, the message
Right, right, right.
Commenting it out is also a possibility.
And, and so like, basically I would tend to, instead of having comments, maybe have a little
bit of terminal output to guide people as, as far as that's helpful, maybe even link
them to a guide.
If somebody's new to a thing, if you're new to our, the way that our route modules work,
here's some documentation about it.
And now somebody, somebody is not going to be new to a project, read the entire read
me and everything about it, but maybe when they're new to a project, they run the scaffolding
command and they see output that links them to the relevant thing with like a minimal
Maybe then they'll click it and read it because it's relevant to them now.
So showing people contextually relevant information, I think is a good technique.
And also like showing exemplary code that makes it easy to change in the way you want
it to evolve.
So like if you have a, if you have a message type that is unit type, type message equals
unit, it doesn't make it easy to evolve that module in an idiomatic way.
So it's better to have custom type there.
And it's better to have your update function do a case expression on the message because
that's what's idiomatic or whatever the idiomatic pattern is for your code base.
So I would say like, try to set up the idioms in a way that's easy to evolve from as a starting
point in your scaffolded code.
Even if that's like a little verbose sometimes, I, I think it's worth it.
And if things are not clear, then it's also potentially API design that needs to come
into play.
Like you're not going to be able to change the Elm browser API, but if it's your scaffolding
in your routes and your route is something that you control, then maybe you can change
the names of the fields of the functions to make it very explicit.
Like, Hey, this is probably what you need to do in order to do what you want to do.
So make good APIs and things will happen automatically.
Yeah, totally.
So for your Elm review scaffolding, what does, what does it give you?
So you've got scaffolding for an Elm review package and you've got scaffolding for an
Elm review rule, right?
And what do those give you?
So the package creates a whole new package.
So it creates an Elm JSON file.
It creates a readme that is formatted in a way that looks pretty much like my other packages,
which is the way that I recommend things.
It comes with the GitHub action scripts.
So if you just push this to GitHub, then you will have a CI.
And you also always have to create at least one rule.
And then for that one rule, it creates a module for the rule and a test module for that rule.
And if you run Elm review new rule, then you also get a new rule and a new test file.
And every time you add a new rule, it will automatically update the readme to list that
rule and it will add it to the exposed modules in your Elm JSON file.
And there's also one file that I generate, which is some documentation of how you should
manage your project.
Like how do you publish this package for the first time and afterwards, because the first
time you have to do it manually, the second time we're using your script to automatically
publish something every time you change the version in your Elm JSON file.
Oh, and also a new package comes with a review configuration.
Yes, very meta.
And yeah, this is one thing that I quite like about it is there's an Elm review rule that
reports comments when they contain some words and it contain, it will flag everything that
contains replace me.
And I put that replace me in a lot of places.
So for instance, every time you create a new rule, that rule will have already some documentation.
It will say this rule reports dot dot dot replace me.
And it says this rule for instance, will flag some code and it will have a replace me there
and it will not flag this code and it will have replaced me.
So I'm forcing the author to at least remove that code or that there was documentation
better to replace it.
So I'm forcing them to think about documentation in a way they don't have to do it now, but
they will have to do it in order for this CI to pass.
Yeah, I think you've hit on another dimension of scaffolding that I think is really helpful
is like, sometimes you don't like sometimes you need to have a placeholder where you say
like, you need to do some like replace this thing and you just you can't fill it in for
them but you can at least make it very obvious that something needs to be done there.
I can fill in the rule name because I know it because I'm generating the prompt for it.
But the documentation I will not be able to do that.
I don't want them to think about it.
You can wire in a GPT API call and take the package name and then generate it.
Maybe maybe.
Next release.
I've also I used that same pattern of the replace me and I think I used your same setup
from your Elm review in it set up there for the Elm package starter, which is a little
template repo, which again, that's another another approach to scaffolding is having,
you know, a GitHub template repo.
I just recently used the Elm package starter and it was really nice.
And I used the Elm review rule to tell me all the replace me spots and then went through
and filled those out.
And it's a nice workflow.
Oh, I'm surprised.
Past Dillon did did a good job at it.
Yeah, I know.
I was pleasantly surprised too.
And so the now another cool technique you mentioned there was like replacing sections
of the read or, you know, modifying the readme when you add a new rule.
That's pretty cool.
So I think now it's also possible to like modify code.
Of course, this becomes much more complicated than like adding a new scaffolded file to
like do like an incision, you know, surgery there.
But but it's possible.
And I think one thing that helps a lot with that is conventions.
If you have clear conventions, and you can just sort of follow like, okay, if these exact
conventions are followed, and I can check that it's safe to to modify this thing, because
I know these exact conventions will have these exact things I can easily check for.
Otherwise, I'm going to bail.
But if you follow these conventions, then I will allow you to modify it.
So that's sort of a cool, like, not like scaffolding within an existing file technique, I think.
As you say, like, it's incision, so you I need to detect like, well, where's the section
where list all the rules, and I'm expecting it to have some kind of shape or try to do
some pattern matching.
And so far, no one has complained about it.
But it works as long as people don't touch it in a weird way.
But I don't know if they know it's a feature.
Well, actually, a lot of people don't use my package scaffolding tool, because they
don't know it takes because they don't know it is.
I used it for creating new packages, and I like it.
Yeah, yeah.
But I think like, the fact that this sort of technique of modifying things easily without
too much fancy static analysis and control flow analysis and type inference and stuff,
I think the fact that that depends on using some very clear conventions can be a feature
not a bug.
Because like enforcing certain conventions and giving you these nice automations you
can use if you follow these conventions can strengthen the cohesiveness of a code base.
In this case, I'm touching the readme, but if I was touching Elm code, then we would
have the compiler to help us out.
If we don't have that, or if the Elm compiler doesn't help in this case, then yeah, using
conventions is a good way around that.
Another thing I played around with a little bit in these scaffolding helpers for Elm pages
is I noticed, again, like inspired by this DHH demo, I was trying to notice all the things
that were slowing me down when I was using the scaffolding.
So one thing I noticed was like originally for the add route scaffolding command I had,
I noticed that I would go in, I do add route with a few form fields, it generates a route
with with a form wired in.
And that's great.
But then in the Elm pages sort of full stack code, where it's receiving that form submission,
and it has the parsed form, I was debug logging it.
And what I found when I was trying to make the demo very short, was that I would forget
to take out the debug.log sometimes, and then I would do the part of the demo where I ship
it to Netlify and show the full stack thing working.
And then it would have an error because the build command was failing because it had a
debug in it.
So that's sort of like not setting it up for success.
And again, not being exemplary code that is making it easy to evolve in the shape you
want it to evolve in.
So I modified it to take this form that it's generating.
So you give the CLI these form fields.
These form fields have types.
The checkbox has a boolean type, a text field has a string type.
So it knows sort of the types.
It generates a record for the parsed form that you can change the scaffolding to map
it into nicer opaque types or whatever you want to do.
But it gives you a sort of good baseline of strings and bools and date types for the parsed
And then I have a scaffolding helper.
So you can take that type in addition to using it to just generate the type alias for the
parsed form.
It can generate a JSON encoder.
And so instead of doing debug.log, I took that parsed form type.
I know what the type is in the generated code, and I generate a JSON encoder for that.
And I send it out through a log, which is not a debug log, but it's an actual back-end
task log that you can ship in a production code base.
So now, and this is all user configurable.
So if your code base evolves and you want to use a different pattern, you don't want
to JSON encode it, you don't want to log it, you want to do some specific thing to it,
you can follow those patterns and change it because this scaffolding API I built allows
you to customize that.
But it gives you the building blocks to generate these things.
And what I found was when I was trying to basically like speed run this demo, I was
more set up for success because actually it was like, oh, actually, I do want to use a
JSON encoder.
But instead of sending the JSON encoder, and then encoding that to a string, and then logging
that, I want to send it through a custom back-end task or whatever.
So I was trying to sort of like use these same idioms in a way that would be like meaningful
and help me guide me in the direction my production code wants to go.
I'm happy with it.
I think it's also a nice pattern to like use types and create Elm CodeGen helpers to work
with types like creating an Elm CodeGen helper to JSON encode a type.
It is something that we can build.
So there's some really cool possibilities.
Like I think building higher level helpers to help generate things with Elm CodeGen is
like a really exciting space.
What should we tear this scaffolding down?
We should.
I did mention it before with GPT and all those AI tools.
Maybe this whole conversation will end up being pretty useless.
I don't know.
We'll see.
I mean, we certainly can guide people to do the right thing better than what we can teach
such a tool to do, especially if we can't teach it's anything, as far as I know.
But I mean, we'll see how those will evolve.
It's a fascinating topic.
My instincts on this.
Now this may be, you know, me becoming an old curmudgeon or something.
I don't know.
But my instinct is that there's value to explicitness.
There are worse things than magic.
Yeah, there are worse things than magic.
Now I think like having explicit tools for scaffolding code still has value even with
AI assist tools.
I'm not saying that AI assist tools aren't also helpful, but if you can create high level
helpers to help you do a very specific thing in a very exact way, you don't have to think
about did this actually give me what I want?
Is it idiomatic?
You don't have to second guess it.
You don't have to double check it.
You just use it like a high level thing.
There's no context shifting.
You think of it as a high level atomic thing.
I'm adding a route with a form.
So to me, it still serves a purpose.
And then I think that there could be interesting opportunities for an interplay between these
sort of scaffolding commands and AI to sort of work together.
And I think that's another interesting space to explore.
Yeah, absolutely.
I for one welcome our new AI overlords.
I honestly prefer my Elm Review overlords because I know what they can and what they
can't do.
And I know that they can't do much about how I live.
And that's a good thing.
I think that like pure functions and static typing, I imagine a world where that is a
superpower for AI assists.
I don't think we're at that world yet, but I think that world could come to pass.
We'll have to see.
Same here.
But that might be a topic for another conversation.
So Jeroen, until next time.
Until next time.