spotifyovercastrssapple-podcasts

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
#53

Transcript

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