Dead Code

We discuss how to remove dead code in Elm, or avoid it altogether in your workflow, and why it matters.
March 28, 2022


Hello, Jeroen.
Hello, Dillon.
And what are we talking about today?
Today, we're talking about dead code.
Dead code?
And what exactly is dead code?
Is it just as simple as it sounds?
I'd say so, yeah.
It really depends on what you have in mind already.
But yeah, the way I see dead code is code that if you were to remove it, it would serve
no purpose.
You wouldn't see any observable difference, like maybe performance improvements.
But other than that, nothing is different.
That's the way I see dead code.
There's also the definition of Oxbow code, so OXBOW, which I think if I understand it
correctly means dead code that used to be used.
It lived a long, healthy life.
If you write a function and you never use it, then it's just dead code.
But if you change your code and then some function or some piece of code ends up not
being used anymore, then it's called Oxbow code.
Do you know what that term comes from?
But no, I have no clue.
I think there's a bag brand that's called that way, but I don't think it's related at
Oh, okay.
So it seems like that is a surprisingly thoughtful definition for something that seems so simple.
But I guess there is a lot of depth to this topic.
Like you say, there's code that once served a purpose and then becomes obsolete.
There's code that was prematurely written, anticipatory code that actually never came
into use.
And then there's another dimension, which is you talked about code that if removed would
not change the behavior.
And so that other dimension is dead according to what perspective.
So for example, if some external system is doing some sort of metaprogramming reflection,
things like that, then something may be dead in a simpler sense.
It might be code that is never executed, but it might change the behavior of some external
system or some sort of tool that's using reflection.
That's why reflection is such a terrible thing in my opinion.
At least from the point of view of someone who doesn't use it, but it does make dead
code detection a lot harder.
I mean, if it uses it in one way or another, then it's not dead code.
It looks like it's dead, but it isn't.
Right, right.
It's a, yeah, that's a static analysis tool author's nightmare, right?
Is a code that appears to be dead, but isn't.
And I guess we've talked about this before, the difference between code generation and
more metaprogramming through reflection and tools like that.
And I guess that's one of the advantages is with code generation, if you look at the generated
code, it doesn't matter at the end of the day, who wrote it.
As long as you can look at that code and analyze that code, it behaves exactly the same as
code that a regular user wrote.
And also the difference with other techniques like metaprogramming or using macros is that
it doesn't change your existing code.
It only changes or it only adds more code, right?
Which is a lot easier to understand.
Like if you change your code using a macro or using some source code transformation to
make it so that one function that looks like it isn't used is now used, then your editor,
your static code analysis tool will have a lot of trouble figuring out that it is used.
So how is dead code different in the context of Elm versus other programming languages?
You've written about this before.
I know you've thought a lot about this.
You've worked a lot in static analysis tools in the JavaScript ecosystem and in the Elm
What's unique about dead code in Elm?
Well, the first thing I would say is different compared to ESLint for JavaScript is that
you can actually detect it.
And in Elm, it's super easy to detect whether code is dead.
In JavaScript, it's a lot harder.
Like for instance, if you declare a variable in JavaScript, you say const abc equals some
And if you can't see abc being used anywhere, then it's likely that it's dead code.
So you can remove the assignment to abc, but you probably still can't safely at least remove
the value that was computed.
So if it was abc equals foobar function call, then you still need to keep that foobar function
You could remove it if you do some very in depth analysis of whether this function actually
did something.
Like is it pure?
Is it impure?
But that's actually very tricky to do or very costly and depends on what your ecosystem
does right.
If you have Babel transforming your code, then maybe you'll move something that will
actually be used or used in a different way than expected.
So that's like the first huge difference between a language like JavaScript and Elm is that
for one, it's super easy to figure it out.
And the other one is hard, almost impossible.
And we've talked about this before.
There could be a Babel like tool in Elm.
They're just that's just not the culture.
Yes, absolutely.
But if we did that, it would break all of these assumptions and it would make your job
writing static analysis tools a lot harder.
I mean, some people maybe already do it on their work project or something.
And I just don't know.
But as long as I don't know that they're using something with that, I'm going to assume that
no one does it.
And if they are getting false positives because of my reports using Elm review and use, obviously,
then they know that it's their fault or that it's their responsibility to make sure that
they don't remove things that should be used or that are actually used.
The nice thing is with the Elm compiler is that when you will try to move it, you'll
get a compiler error anyway.
Depending on how their system works.
But yeah.
I was just thinking that, too.
The worst case scenario is a lot different.
And the likely scenario is a lot different.
Likely you don't have to think about metaprogramming at all.
But even if you did, the worst case scenario would be that you get a compiler error, not
a sneaky bug in production.
But it also depends on what kind of transformations are done.
Like if someone tomorrow comes out with a Babel like project for Elm and they don't
have anything that would impact unused codes or dead code detection, then it's not an issue,
So let's talk about some of these rules.
So in the package Elm review unused, which we'll drop a link to, let's just kind of step
through what some of these rules do.
So let me just list them off real quick.
So the rules in this package are no one used variables, no one used custom type constructors,
custom type constructor arguments, exports, modules, dependencies, parameters, and patterns.
So those are the core building blocks.
And they also sort of play off of each other, right?
Because by removing one thing in one step, that allows another rule to kick in and say,
well, now that that thing is not used, this other thing isn't used.
Like every time I improve one of these rules or I write a new one, my hope is that I'm
starting a Ruby Goldberg machine.
So one rule removing something and that then triggers something else that removes more
stuff, more stuff.
And hopefully at the end of it, I've got like a snowball effect.
So one tiny thing and then you'll end up removing thousands of lines of code.
That's what I'm hoping for at least every time.
I feel like no one used variables, exports, those are things that are pretty intuitive
and you can wrap your brain around.
I'm always impressed when I see that it's able to detect that a custom type constructor
is unused.
So you want to talk about that a little bit?
The way that you determine that is pretty clever and maybe not immediately obvious how
you would do that.
So in Elm, you've got custom types and custom types lists a bunch of constructors or variants.
So if you have a type remote data, you can have a loading constructor, you can have a
loaded constructor and an errors constructor.
Let's go with these three for now.
And the nice thing about Elm is that every time you use one of these, it's done explicitly.
So it's never anonymous.
So if you want to use one of these, you will actually see somewhere saying loading or loaded
or error.
And because Elm has an explicit way of referencing variables and functions, you can always work
out which type this refers to.
There can be some confusion because there are two modules and it define a loading state,
a loading constructor, but it's always explicit with the imports or not explicit, sorry, unambiguous.
So whenever it would actually be ambiguous, it could be either one of those, that's a
compiler error.
Because of the shadowing changes in Elm 19, right?
So if it's ambiguous, we would do something or we do nothing.
It really doesn't matter because Elm Review assumes that the code already compiles.
And so whenever we find a type that matches this constructor, we just report it.
And there are some, yeah.
So if there's a variant that is never used, then it can detect that.
And there's a special case with comparisons, right?
What's the subtlety there?
So before we talk about comparisons, there are two ways in which you can use a variant.
You can use it as a value, like you assign it to something, you store it in your model,
something like that, and you pattern match on it.
And the pattern match actually doesn't count as a use of it.
So if you handle the case, but you never build one of those, you have to use them, then that
is actually a dead code block, a dead code branch.
And you can remove both of those.
You can remove the definition and you can remove the case of that branch.
If you consume something, but you never create it, then it doesn't, all the places that you
consume it don't matter because you can prove that it's never created.
And this, of course, is different if it's an Elm package, then you're exposing something
externally to your known code base in the Elm package.
So you don't know if it's ever used if you expose a custom type constructor.
But if you, if it's not an Elm package, then you do know whether it's ever used.
In a package, anything that is exposed publicly as part of the packages API is considered
to be used.
Any functions in there.
And so yes, there's an edge case about using these constructors in comparisons is like
if you do a equals equals loading, but that that's in a way just like pattern matching,
That's just checking that a is of type loading.
So you can consider these, or at least I do, I consider these as non users.
That makes sense.
So even though you're technically creating one, it's only to do the same ideas, pattern
That makes sense.
And this rule also provides an automatic fix, at least when the type is only used in that
same file, because Elm review has only single file fixes.
And in those cases, the comparison gets transformed to a false or true.
And then Elm review simplify can clean that up.
And then Elm review simplify can clean that up.
So that's like this general pattern of these review rules piggybacking on each other and
Which feels so nice.
Although it's sometimes hard to know like which one should take care of what, but it
works out usually.
So how do you think about, I think that, you know, we tend to get attached to the code
we write, you know, I'm sure you have mixed feelings when Elm review is telling you that
something is unused, right?
You're celebrating and just party.
Well, you're more enlightened than most of us then.
But you know, you want to argue with it.
You want to say like, wait a minute, like, I know that's not used, but this is like,
I defined my type so nicely and I'm going to use it and this is like the platonic definition
of this type.
And sure, it's not used right now, but it should be defined this way.
So like, how do you navigate that when you have a type that it's like it's not used,
but like it feels like it should be defined that way.
Have you had discussions like that from users who are?
Yeah, definitely.
So Mance is not always just remove it.
That's my default action though.
It's not what I always go for.
Sometimes I noticed that a function or something is not used and I reported to the one who
wrote the original code and they tell me either like, okay, yeah, this is oxbow code so you
can remove it.
Or this is a bug.
Like oh, this actually should be used.
Like if we don't do it, then that's a bug.
Like for instance, having a function that checks whether you're admin or not and you
know there are sections where you should check that, then yeah, you probably have a bug lying
So that's actually a very nice feature of the whole unused package of all the rules
is because one thing that you get out of it is that you get a lot of potential bugs reported
to you that are not always reported in other languages and other tools.
So that's like my main, one of the main reasons why I do care about it so much is because
it's actually a very good heuristic for bugs, for finding bugs.
But yeah, it depends.
The answer is not always to remove the code.
It also depends on how much complexity there is in the code and how much harm there is
in keeping it and how much harm there is in removing it.
So one example where I could see myself keeping it is if you have a bunch of colors, like
you have a color palette somewhere, you define red, red 100, red 200, red 300, according
to some palette that you defined or that came from your designer.
And a lot of these will be unused, which is normal, but does it make sense to remove those?
Maybe how much trouble will you get if you keep them?
Probably nothing, right?
But on the other hand, it's also very easy to add again, probably if you have access
to your style guide or design palette that comes from your designer.
Where I would say I just always delete it is when you have features that are half built,
but are not currently being developed because for me, it's a sign of premature, not optimization,
but premature design.
So you're thinking ahead to build a feature and you think you're going to build it this
way, but it might turn out that that is not the right way to go.
Right, it's Yagni code.
You're going to need it.
So if you want to keep it, then you can keep it, sure.
But you're going to have to maintain that code and maybe in the end, remove it anyway.
And maybe because you had all of this code beforehand, you will keep it, you will try
to change it when you're developing the feature once you're actually working on it.
But if it was not the right design, then you're going to spend more time than if you wrote
it from scratch, potentially.
It's a hard science, right?
I really like that framing of it.
That color example is a really good one.
This represents a meaningful concept in your domain that even if it's not being exercised,
it is a correct definition that's meaningful.
And then versus building features partially in advance, essentially work in progress.
I think a lot about this lean thinking concept of work in progress and how that creates waste.
In lean, there's this idea that work in progress leads to different types of waste like defects,
extra maintainability.
But it also just reduces your capacity to move quickly on the things you are building.
The idea would be to do small batches of work where you actually build things that are used,
not building a bunch of partially used things.
If you're creating a lot of unused code in the way that you're building something, as
you pointed out, there's this oxbow when code becomes obsolete versus when you write something
– when you write a lot of unused code as part of building a feature.
Those are very different.
And if you're building a lot of unused code as part of how you build features, you might
want to reflect on how that's having hidden costs to the way you write code because essentially
you're cutting yourself off from a feedback loop.
If it's unused code, that means it's not being exercised by an automated test.
That means it's not being exercised by code that you're seeing in the browser when you
run through how things are working.
That means it's not being exercised by users who are giving valuable feedback on whether
it's solving their problems.
So yeah, that's really important to try to minimize unused code.
But as you say, there are times when – for example, if you define an API and it has map3,
map4, map5, map6, map7, map8, and because you like using that pattern in your code base
and map7 is unused, that's probably fine, right?
So that kind of belongs in a separate category.
That is not building features prematurely before they're exercised.
It's defining an API.
So it's kind of like an internal package in a way.
Yeah, absolutely.
Yeah, those are a bit tricky to figure out.
Is it better to remove it?
How much risk is there in messing up, rewriting map7?
Well, there's some risk.
But the other solution is, as you said, if you write automated tests, then it will be
considered as used, at least in the current workings for ElmGy.
So just write an automated test and you won't have to remove it.
Now, does that mean that having automated tests around something that doesn't actually
get used in production, can that prevent you from getting that snowball effect where a
bunch of dead code is removed?
Yeah, absolutely.
I mean, we could ignore tests in practice.
The thing is, it's going to have some unexpected consequences or you'll get false positives.
So basically, if you have a function that you only use in tests, and you remove it,
or you report it and then you remove it, maybe that's fine.
But there are also some values that you only use for tests, like explicitly for tests.
Like if you want to test the implementation of a module, it's not the best thing, but
in some cases, you won't need to.
For instance, if you have values that you can only create through HTTP requests, for
instance, or GraphQL requests, those you can't test if you don't have a way to write them
or to create them.
So you will probably add a function to create those values specifically for tests, and then
you have one way or another to make sure that they don't appear in production code.
But those would be reported as unused as well in your test, in your biome review unused.
And that's a false positive because you want to keep those.
So that's like the only thing that prevents me from working on this.
Like I don't know what I should do with this.
That makes sense.
Yeah, you might have like a helper to create an admin user, and maybe you even also have
an Elm review rule that gives you an error if you use that helper outside of tests.
But so yeah, what do you call it?
Do you call it unused or not?
It's not an easy one, but it does make me wonder, would it at least be interesting to
try running, like to delete my tests folder and run Elm review unused and just see what
And then of course, very quickly go restore my test folder so I don't accidentally permanently
remove it.
But you know, that could be interesting.
I think that could be interesting.
And if some people try it out and report back to me how useful that would be, probably quite
a bit actually, then maybe I could write some configuration settings for no unused exports,
which I think would be the appropriate rule in this case.
Like it could have a configuration setting to say these should be reported, these should
not be reported or something like that.
Or you should ignore tests, you should not ignore tests.
So, and that said, I mean, you could also argue that it's a code smile to depend on
internal values from tests.
It is, it is.
But in some cases, like, yeah, you need to, I think.
You have to use your judgment.
Yeah, you have to use your judgment for that.
But if you're reaching for that solution too often, then it might mean you need to rethink
the way you're designing your APIs, your public interfaces for that code.
So we talked about kind of preemptive code, anticipatory code versus Oxbow code, code
that becomes obsolete after having been used.
But what about, and we talked about the cost of introducing unused code as part of the
But what about when to remove unused code, like how long should you go before removing
unused code?
Should it be you make a pull request and you clean it up as part of the pull request?
Should it be before you commit your move unused code?
Should it be even within making a single tiny commit, you have Elm review, dash dash watch,
dash dash fix all running in the background and cleaning up unused code?
Like how far do you take that?
The longest I would wait is for when doing the pull request.
Definitely at the level of pull request.
I know that some companies that have very large code bases do a somewhat regular cleanup,
but that's probably just because Elm review is too slow for them.
In all the other cases, I would at least make it require that all unused code is removed
in the CI.
Every pull request needs to have removed everything.
The CI should fail because that Elm review rule will have errors.
Yes, exactly.
And then do it at every commit or do it within a single commit.
I think it depends on how you want to do your commits.
Like if you're an adept like us of tiny commits, then you can actually choose to do it per
commit or per change or not.
Depends on how you do your commits.
I probably write my functions before I use them.
And I think that you probably write some dummy code, replace that by a function.
So you extract that to a function and then write the contents of the function.
So in your case, you would never have dead codes.
Or very little.
I will say I actually practice those things.
I think a really powerful technique is doing code caddas to really practice this sort of
Yagni, you ain't going to need it mindset.
So if you think about test driven development, there's this idea of do the simplest thing
to get a test green.
And it's always there's this concept of programming by intention where you start with an intention
and then you write the code to fulfill that.
So you like write a test.
It's failing.
It's saying it should do this.
So it's used before it even exists.
So using it comes, of course, there's non use code because it always starts by using
It's a pull versus push.
So instead of pushing out code that you think you'll need, it's this philosophy of consuming
code before it exists and then only creating it in response to that.
So you're always responding to a need just on every level.
You're trying to respond to user needs by building things that you actually validate
users want.
And you're trying to respond to the needs of your code and your architecture and your
infrastructure by building things as they're needed.
So that's something you can practice and get better at.
And I have found that approach to be extremely powerful.
If you do it my way, then you write a function where I think it's probably the more common
You write a function and when you're done, you use it.
That is a different type of dead code that we haven't talked about it, which is code
that you wrote and that you forgot to use, which is actually pretty common, I think.
At least with Elm review, you get a reminder that, hey, you forgot to use this, which you
would also get after a few minutes of debugging.
Like why is my feature not working?
I wrote the function perfectly and like, yeah, you forgot to use it.
I still need to train and to write things the way that you do, I think.
Because I think it works better.
But yeah, if you do it in my lazy way, then do it whenever you feel like it.
Like when you want to clean up, if you do it, if you write comments your way, then I
don't know.
Like I think it would actually be very interesting to do an exercise where you would write commits
in very, very tiny steps as you like to do, and you try to fight against Elm review unused
and Elm review simplify.
Yeah, it would be fun.
And see how you can circumvent it or whether you could win.
That would be fun.
I suspect that the one thing that would really get me would be writing types, defining types
before they're used, because I do often model out types before I use them.
But I think it's worth sort of comparing, like you can write unused code or you can
write a to do list, right?
And often I think maybe people don't realize how important a part of that process that
Like I am constantly writing out new things on my to do list because I know I'll forget
them as they come up.
So that helps whenever I think of something instead of writing some code to do that thing,
I say, oh, make sure I solve this problem.
Make sure I don't forget about this thing.
Sometimes I put to do's in code.
And I mean, sometimes I have if I'm going through and I'm trying to fix a bug or change
a feature or something like that, sometimes I will comment out code and put to do's surrounding
that code because I know I need to consider a case or make sure that I actually do want
to remove something.
But having lots of commented code, Elm review unused, at least right now, will not detect
any commented code.
But there is also a cost to commented code.
And I think that's also there's a cognitive cost to that because, you know, it's an in
flight piece of code that you have to look at every time you look at that code and wonder,
was there some action I was supposed to take here?
And it's not clear what the action is from that commented code.
But I will I will often go through and comment out code and then delete it before I make
a tiny commit.
I actually don't see a lot of commented code in Elm, which makes me very happy.
I know that in ESLint, some people I worked with in the open source community wrote a
rule to report commented out code.
In my opinion, that probably has too many false positives, especially with Elm code
because like a regular sentence can be vetted Elm code.
There aren't as many curly braces and parentheses and everything.
Yeah, just a sentence without the period at the end.
If you just have spaces, then that's just one function call.
So yeah, I think there will be too many false positives.
And it's not a problem that I see often, at least in the projects that I work with.
Yeah, yeah.
You know, I'm realizing now this idea of building out partially complete code versus like maintaining
a to do list.
It's actually very similar to like the concept in the getting things done productivity methodology.
There's this idea of inbox zero, which you may have heard of.
I'm guessing you keep your inbox to zero.
Zero unread messages.
Yeah, zero unarchived messages.
So you clear out your inbox.
And the way you do that, here's the subtlety.
So you have your, let's say, email inbox could apply to different kinds of inboxes.
And you sit down to process your inbox.
And all you are trying to do is process everything, not necessarily complete everything associated
with it.
So there's this idea of the two minute rule, if you can do something in two minutes or
less, you take care of it.
So if you can respond to an email and be done with it two minutes or less, do it.
That's how you process the email.
Everything takes longer than two minutes, then what you do is you take that as an item
on your to do list.
And then it goes out of your inbox and into your to do list for larger things to track.
So I think there's like a similar dynamic with code where if you have a lot of in flight
code that is partially complete, it's it, I think it's a little bit overwhelming to
have all these things that are in varying states of completion.
But if you just process, it's like getting to inbox zero, like finish the things you're
working on, capture the things that you don't want to forget.
And if you trust yourself to have a system where you know, you're going to capture the
things you want to be sure not to forget, then you don't have to write partially working
code to to make sure you you capture it while you're thinking of it, you just put it in
your to do list.
I mean, sometimes you just want to write a function because you finally figured out how
to write it, for instance.
Slightly different, I think.
You could you could put it in your to do as well.
No, you're right.
And that I'm glad you bring that up.
Because so often, like in the sort of, you know, codecraft community, people often make
the distinction between like, writing production code and spiking.
And when you're doing a spike, a research spike, you're just experimenting and the deliverable,
the thing you're trying to achieve from that spike is trying to figure like, figure something
out as quickly as possible, like is, is this viable?
Is this performant or whatever your goal is to understand that research task, but then
it the idea is that it can be sort of throwaway code.
And so that's that is a different mode of writing code.
So you probably put it in your to do somewhere at some point.
Yeah, and you can, you know, maybe you're writing unused code, and you're just sort
of, I mean, to me, that's like a different mode of coding, then building out production
code that I sort of have a clear path that I know how to build.
It's more like, I don't, I don't know how I would build this.
I'm just gonna throw a bunch of code out there and experiment.
And the idea is that it's sort of like, you could just throw throw the code away.
So elm code, dead code in elm is interesting, right?
Because it doesn't affect your bundle size.
So what why does it matter?
So elm has dead code elimination.
So it removes all the code that is unused.
I think it was Mario Wojcik who liked to call it live code inclusion.
So it's not that you remove all the dead code is that you start from your main, and you
include all the functions and types, or you actually only the functions and values that
the references and you do that over and over again until you've collected all the references.
So that, yeah, so you pull out whatever comes from main instead of removing everything from
your code.
And that works well in elm.
So it will work extremely well in the sense that you will have, if you have a lot of dead
code, you will remove almost all of it.
But there are some cases where elm actually doesn't remove dead code.
And that is, for instance, for references that are defined in led variables.
So if you define a led variable with underscore, so an unused thing, see underscore equal something,
well that something will be included in the bundle size, in the bundle.
So it will impact the bundle size.
If it's assigned to something else, like param or abc, then even if that is unused, it will
also be included.
And there are a bunch of these things where, well, that's the primary one.
But elm doesn't do, let's say, a good enough job to remove everything.
Same with unused record fields.
If a record field is unused, it's still going to pull in the associated code.
So if you unused doesn't target those yet, we can talk about that in a moment.
So yeah, there's an issue that elm doesn't remove everything.
But also, the biggest reason for removing dead code, even though you've got dead code
elimination, live code inclusion, is for maintenance.
So if you have a type with four variants and you only use three of those, but you have
a panel match for the value in 100 places, then you've got 100 branches that will actually
be included in your source code, that will be shipped, and that you will have to maintain.
So if you, yeah.
Yeah, you have to pull that into your brain every time you look at the code, right?
That's huge.
So one thing that I would say probably the number one elm review unused rule that I fight
against would be no unused parameters.
Because like, well, I mean, I don't know, sometimes as a framework author, sometimes
it's difficult because I'm passing in parameters.
And I'm like, these things are available.
And then elm review unused is like, yeah, but they're not being used.
I'm like, yeah, but like, you know, there's not an easy answer there.
But as a regular user, I also find that sometimes I, you know, or sometimes I'll pattern match
a constructor and I'll destructure a variant and I have these three values and I want to
know what their names are when I'm looking at it, but I'm not using one of them.
And elm review unused is like, no, but you're not using that.
You should call it underscore, which I'm like, yes, I agree.
But also I want to know what its name, I want to know that it's not used clearly in the
I also want to know what its name is.
Yes, I know.
I feel the same about this somewhat.
So the issue is that you've got code.
Sometimes you have functions that you need to define that need to conform to a certain
API, like, for instance, takes the key and the value.
And often you only care about one of those, but you cannot remove one of the arguments.
Like you have to declare to, even if you put underscore.
And elm review unused is not smart enough to know, hey, you should, and elm review unused
is not smart enough to know when that is the case or whether it's a custom function where
one of the arguments is actually just unused and you could remove it.
So the only thing that this rule aims to do is to let you know that an argument is unused
so that you can potentially remove it from the function definition as well.
So if you have a function that takes two arguments and you're only using one of them, then it
will tell you, hey, your first argument is never used.
Either remove it, which would be amazing or rename it to underscore so that it's like
an active choice of, hey, this is unused, I know, but I don't have a choice.
Like this is the interface that I'm working with.
I know that some languages have a different way of handling unused variables where you
can name your variables with a starting underscore.
And that means that this is unused.
Like you won't be able to use it.
At least you can name it, which I think is really nice.
I think so too.
Yeah, exactly.
Elixir has that feature and I've really enjoyed using that where you get to give it a name,
your tooling without doing any fancy static analysis knows right away that it's an unused
thing because it's actually syntactically, as you say, unusable.
It's the same as throwing a value away with underscore, but it can be given a name.
But even in that case, like if the compiler or the static analysis tool notices that something
is unused, you will have the option of either prepending it with underscore or removing
And once you've prepended underscore, well, the tool won't be able to tell you that it's
unused again.
So if you're not working against an interface later on, then you will have an unused argument.
So it's like an active choice that it will be somewhat definitive, which is a shame.
If you do a sort of underscore throw away on a parameter in an Elm function, that doesn't
get dead code eliminated or anything like that, right?
The Elm compiler would still treat that as used.
So that's a good thing to be aware of.
I don't know.
It's not obvious what to do in cases like that.
I have been kind of mulling over in my head recently whether records can be helpful in
cases like this or whether that's a bad idea.
But that's something I've been thinking about.
I think you would mostly be moving the problem away.
Just moving it further.
I mean, sometimes records are sometimes you want a group of named things.
I mean, overusing positional values can be not so great.
And honestly, I think I do that too much in my own code.
I think that I do too many positional variant arguments.
And I think I do too many positional function arguments probably.
I mean, to a certain extent, it is what the syntax makes natural.
And when you define a variant in Elm, it gives you a constructor function that takes each
positional value as an argument.
Whereas if you define a type alias for a record in Elm, it gives you a function where each
positional value is each of those named fields.
Pairs like something like Ruby, they have these key value hashes sort of built into
the way you do method invocations because you can just pass in key value pairs in a
very natural way that's built into the syntax.
And so it encourages naming arguments in that way.
Yeah, I don't think that would work as well in Elm because of currying.
Yeah, you're right.
Because in Ruby, when you call the function, you will have all of the arguments already.
And in Elm, you have partial application, so you may only have a portion of the arguments.
That's a very good point.
And also, you can't extend an Elm record or change any of its types.
So you can't really modify the shape of a record.
So yeah, you're right.
Unless it's really a distinct record that has like a clear purpose of related data,
you probably shouldn't just throw unrelated parameters into a record in Elm.
Yeah, but I think it's also an issue because if you don't have ID support for making this
Like it takes a lot of effort to change positional arguments to a record or the other way around,
because you need to change all the locations.
In my case, I usually do an entire commit to do just that and then add more arguments.
But if you had ID support for that kind of transformation, you would probably end up
with a lot more records.
You would do this change more often.
Same with parameters.
Removing unused parameters if there was ID support for that or for adding a new parameter
and drilling that down, that is probably one of the most tedious parts of writing Elm code
for me right now.
If I need to pass some data through deeply, then it's tedious.
So unused records.
So you mentioned, I know you've been working on this rule for a while.
So what's the big challenge for writing this rule?
Yeah, so I'm not actively working on it or I'm working on it on and off because it's
So I think that the main issue is that records in Elm are anonymous.
And I think I said unused records to clarify, we're talking about unused record fields.
Yeah, because an unused record is just a value and that can be removed easily.
That's the way the case.
So yeah, the records in Elm are anonymous.
So if you go back to the custom types, when you have remote data equals loading loaded
error, if you see loading, you know for sure that this is the loading from that type, that
this is error from that type because you can work it out.
The imports are explicit enough and unambiguous enough.
For records, you don't have that.
So if you, for instance, have your type alias model equals records with some fields, then
you can try and figure out which fields are used and unused.
But it's going to be a bit tricky.
You will need type inference, which I don't have in Elm review so far.
The issue, for instance, if you have a list of items, an item is a record and you pass
that list of items to a list on map function.
Well now you need to know what the type is of the function that you pass to list on map
to know what values are used for the items.
And depending on that, you will be able to figure out what is used, what is unused.
So that is one issue that we need type inference.
But there's also the issue that you define a record without a type annotation and you
don't know whether it will be used as an item or as a model later on.
So that makes things a bit tricky.
Like if I remove, if I report this field from this type, maybe not including one of the,
it's a bit hard to explain.
Basically, basically you're using a, you're seeing an expression somewhere and it uses
a field from the record, but you don't know whether this record is corresponds to model
or item.
It might be a different record or a different type.
And it's a bit tricky to assign those because it's anonymous.
Like there's no explicit way of linking the type and the record that you built in the
value realm of Elm.
That makes sense.
So it's doable, but you need type inference and potentially also like flow, understanding
how a value flows from one function to another.
So it would be cool though.
It would be cool.
But so, okay.
So there's another rule that we haven't mentioned.
We mentioned all these.
Maybe I'll just work, say something in between.
So I don't have an Elm review rule for this, but I'm working a tiny tool to report unused
Using Elm optimize level two, you have a list of transformers where you analyze the JavaScript
code and I made a very simple transformer that basically looks at all the field uses
in the JavaScript code and all the field declarations.
So if a field is never used, but you find it in some places, well you can remove it
because Elm is very explicit.
Like you don't have any dynamic property access.
So if in the whole bundle of your code you never use the field ABC, then you can remove
all declarations of field ABC.
So I wrote a small transformer that just removes all that code and also reports it back to
So I've used that recently to find out a few unused fields in my code and that worked pretty
Oh, that's cool.
So you can remove all those positives, like code that is in dependencies that you have
no control over.
But it's also pretty nice to have this in Elm optimize level two, if I one day make
a poor request and get emerged.
Because then it will remove even more code that you have control over.
Yeah, the snowball gets a little bit bigger.
But then again, you need to build a snowball mechanism because some field doesn't get declared
so you don't have the assignments.
But now the value that was assigned, if it was using a function that is now never used
anymore, you also need to remove that.
You need to do the live code inclusion or dead code animation again.
And so on and so on.
And that Elm optimize level two doesn't have that yet, or it doesn't have it enabled at
So a bit more work.
But it would be cool.
That would be very cool.
Yeah, there's so many good rules, even without the unused record fields.
The unused dependencies is incredible.
Martin Stewart even has his bot that will pull requests on Elm packages that have unused
dependencies, which is awesome.
If you get such a poor request, please accept it.
Because if you don't, then it means that people who depend on your package probably depend
on dependencies that they don't need to depend on.
And since you can't have two major versions of a single package, that can actually block
people from using your package with something else.
And also just it's cleaner, it's more lightweight.
It's nice.
Accept it, please.
It's so nice that imagine if there was a way to say no unused JavaScript package.
I don't know.
Maybe there is.
Maybe there is an ESLint rule.
Yeah, I wrote one for ESLint.
Oh, cool.
It's called no extraneous dependencies.
Is it as robust?
It's very robust.
But it might change your code.
Because importing a dependency means importing a module.
And importing a module means it can have side effects.
So yeah, it can have false positives in that way.
Whereas in Elm, nope.
And import doesn't do anything.
That is so nice.
I know.
It's amazing.
And one last related thing that we haven't talked about.
Maybe there are other related Elm review packages.
I'm not sure.
But Elm review simplify kind of rolls the snowball a little bit more.
And actually, I think I under use Elm review simplify.
So I mean, the more you can roll that snowball, the more you can roll the snowball.
So that's the snowball effect is it's like, I don't know, grows nonlinearly or something.
So tell us about this roll.
What does it do?
So yeah, there's a bunch of ways that you can write pieces of Elm code.
And some are more complex than others.
And sometimes in unnecessary ways.
So if you do something like a plus zero, then that's the same thing as a.
So I actually don't know if Elm review simplified handles that one, probably.
Let me just check.
Yeah, so it handles that one.
So that's the kind of transformations that you can expect from this package.
Just simplifying the code to more simple code, right?
So why would someone have a plus zero in their code?
Oxbow code.
So I always like these are just seem very stupid or simple in some cases.
But I always try to view it as this can be the results of another rules impact.
So for instance, let's say you have an if condition where in one of the conditions you
have you return one in one of the branches return one and the other one you return zero.
And because you removed a constructor custom type constructor, for instance, that gets
replaced the condition gets replaced to false.
So you got if false, then one else zero.
Well, Elm review simplify will simplify that to remove the if condition and just keep the
last branch.
So now you're left with n plus zero, which you can again be simplified to just n.
So it's essentially another dead code removal tool, right?
Yeah, that works in a slightly different way.
And there are also some trade offs with this one, because you don't want I don't want to
impact how the developers write their code too much.
Like for instance, if you have a list of map plus one and then an array with one, two,
three, then I could replace that by two, three, four.
But potentially that makes the code more harder to read.
Like especially if you do something like if you have large strings and you compute them,
then I could kind of pre compute them.
But then the code would be super long and super hard to to maintain.
And it might make the intent of the code less clear or yeah.
But it's more like this package is more like if the value can already be simplified or
computed and there's not a lot of gain in keeping the original code, then I simplify
For instance, if you do string dot length of a string literal like ABC, you know it's
I don't see when you would actually need to to keep it the original way, except if ABC
was somewhere else.
But then you can replace it by a string, by a variable, in which case it won't simplify
That's an interesting one, because like if you have minutes per hour equals 60 and seconds
per minute equals 60 and hours per day equals 24 and then you have seconds in a day equals
those three things multiplied together, then you could simplify that.
But you know, maybe that it was done that way intentionally to to not have magic numbers
to write like magic numbers is a code small people try to avoid.
And so there's a trade off there.
And in this case, I don't do it.
I don't simplify it.
Because otherwise it would make the code harder to read.
And some some intent would be lost.
But if you multiply by zero, then yeah, I remove it.
Yeah, I simplify it by zero.
Like whatever your intent was next to it, if you multiply it by zero, it's zero.
There's no point in keeping the other things.
That's very cool.
Yeah, we'll link to all these the unpackaged documentation for Elm Review simplified lists,
examples of everything it does.
And it's, it's pretty cool.
I need to I need to go run run this through my projects and see if it gets rid of much.
We'll see it'd be interesting to see what it what it finds.
I'm always disappointed.
It doesn't usually find that much.
And then there's also the question of, you know, using this as a as a refactoring aid.
And that's interesting as well.
But I know that these some of these simplifications are like the opposite of what you do when
you write your code.
Because because review fixes are unidirectional and refactorings are bidirectional because
sometimes, sometimes you want to inline code, sometimes you want to extract code and they're
they both have their purpose.
If we go back to unused rules real quick, I just want to give a shout out not to someone
but to a thing which is called shadowing or the absence of shadowing.
So one of the reasons why Elm is such a beautiful target for Elm Review unused is it makes things
so much easier is because you have no shadowing.
You can't really declare a variable in the same scope and that removes a lot of ambiguity
and therefore complex logic in the rule to detect unused code.
So I'm super happy about that.
So I know that a lot of people don't like it because it's annoying.
But I think like even just without the unused rules, there are some very good arguments
that Evan makes in his error message in favor of this feature.
That blew my mind when you described how it makes code easier to reason about because
you can't, you now can't delete a definition, a let binding or something and then have it
change the meaning of your code.
It could like not compile but it can't change the meaning of your code.
And similarly Elm Review doesn't have to do flow analysis now because it can rely on that.
That's pretty, pretty powerful.
The only thing that I would actually like is for Elm to do more of it.
Right for basics and all the automatically exposed values.
So like even just explicit imports, if you import a text from HTML for instance, that
can actually be shadowed.
Oh, yeah.
So anything that you import from basics or from any other imports explicitly or implicitly
can be shadowed.
And therefore there's quite a bit of complex logic in Elm Review to handle that.
So I'd like to get rid of that if possible for the same reasons as shadowing.
But I do understand that then some imports can be annoying to do but qualified imports
for the win.
Are there any Elm Review rules to help with preventing shadowing in those few cases where
it's possible?
But that seems like that could be a good candidate for a review rule.
Good idea.
So if you weren't aware of this, if you define a, if you import a value and you define another
value with the same type, so like probably if you define text, it's going to be a string,
but the text from HTML is going to be lists of attributes, lists of children, returns
So that's not an issue.
But if you define something that has the same type as something you import, then removing
one of them or removing your local one can have behavioral changes.
So you need to be aware of that even though it's super rare.
Like I don't think it's ever happened to me ever actually.
But it can happen, you know?
The aliasing multiple modules under the same name is a little bit like messes with your
head too, right?
Because I'm like, what if they have functions or types of the same name?
Oh, that's not an issue because that's a compiler error.
That's a compiler error.
That feature always seemed really surprising to me that it exists.
I guess, yeah, I know some people like to say, you know, import list.extra as list or
something so they can...
Yeah, I don't like it, but I'm also...
I also have the point of view from someone who had to write code against that.
Right, right.
And there was a time where MReview didn't report those.
Because it didn't know about like other files.
So, oh, well, I'm importing stuff from these two modules that are aliased to the same thing.
I don't know what I import from which one.
So I'll just keep them both here.
And then like a few versions later, then I was able to successfully remove those, which
felt so good.
Well, there's a lot of satisfaction in removing things.
Because Elm review unused gets a lot of love, people love using it.
If you haven't tried using it, there's a command at the bottom of the readme, which we'll link
to for how to try it out without installing anything or setting anything up.
You just run it and you can feel guilty about all the unused code you have.
Or celebrate.
Or celebrate.
I mean, it's something that we often see now in tweets or on the Elmslack is people
just pasting a screenshot of how much code they just used.
And I think we all kind of know that this is because of Elm review.
Yeah, yeah.
You're definitely...
Also somewhat the Elm compiler.
Right, right.
When it's massive deletions, yeah, it's Elm review.
It is a good feeling.
It is a good feeling to do some spring cleaning.
And what better time than now to get rid of some dead code.
Because if you have Elm review unused enabled for every...
If you have all the rules enabled, in addition to having the guarantee that your code works,
that it compiles, you also get the guarantee that whenever you see a value, it is used.
Whenever you have a function, you know that it is used.
It is one less question that you have to wonder about.
So for instance, whenever I see a pull request coming in, and someone defines a function,
and I haven't seen it used anywhere yet, I stop wondering, is this used?
I know that it is used because otherwise there will be a CI error.
That's a nice feeling.
It is.
That is a powerful...
It's a constraint about your code that you can rely on to make it easier to reason about
what's going on.
Well, I think we've covered dead code pretty well.
Is there anything we should leave listeners with to go explore more or read up on this
Well, I've got a few articles on my blog talking about this in more detail.
The more interesting one, I think, is a safe dead code removal in a pure functional language,
which explains all the steps of how do we actually know that something is unused and
how far can we go?
Yeah, that's where I would send people towards.
Yeah, there's another nice one, which is, you thought you had no dead code?
Also worth a read.
All right.
Well, happy dead code removal and Jeroen.
Until next time.
Until next time.