spotifyovercastrssapple-podcasts

Large Elm Codebases with Ju Liu

Ju Liu joins us to share some tips and techniques from working with a large Elm codebase at NoRedInk.
February 14, 2022
#50

Transcript

[00:00:00]
Hello, Jeroen.
[00:00:02]
Hello, Dillon.
[00:00:04]
And today we've got another guest with us.
[00:00:06]
We've got Ju Liu joining us.
[00:00:08]
He works at No Red Ink, and he's
[00:00:10]
going to be helping us talk about
[00:00:12]
large codebases and maintaining
[00:00:14]
large codebases in Elm. Ju, thanks so much
[00:00:16]
for coming on. Thank you for having me.
[00:00:18]
It's a pleasure. So, yeah,
[00:00:20]
I'm really excited to have
[00:00:22]
you on and excited to hear about
[00:00:24]
your experiences working on
[00:00:26]
the No Red Ink codebase.
[00:00:28]
So let's dive in.
[00:00:30]
Maybe we could get a little bit of background.
[00:00:32]
For anyone who doesn't know, do you
[00:00:34]
want to tell us a little bit about what
[00:00:36]
No Red Ink is, what Elm
[00:00:38]
looks like big picture, how much Elm
[00:00:40]
there is at No Red Ink. Let's kind of get a little background.
[00:00:42]
Sure, of course.
[00:00:44]
I think for all intents and purposes,
[00:00:46]
we're an educational platform.
[00:00:48]
So most of our
[00:00:50]
users are either teachers
[00:00:52]
or students. And we've been
[00:00:54]
writing Elm for our front end
[00:00:56]
for many years.
[00:00:58]
I would guess since
[00:01:00]
2015, but maybe
[00:01:02]
some other experiments are even
[00:01:04]
before that. So, and
[00:01:06]
I would say 99.9%
[00:01:08]
of our front end
[00:01:10]
code is written in Elm.
[00:01:12]
We still have few lingering pieces
[00:01:14]
of various JavaScript
[00:01:16]
frameworks just lingering
[00:01:18]
in the darkness.
[00:01:20]
But no new code is written
[00:01:22]
in those frameworks any
[00:01:24]
longer. Those pieces are waiting to be
[00:01:26]
discovered.
[00:01:28]
To fight the newcomers.
[00:01:30]
I think, you know,
[00:01:32]
every time we do
[00:01:34]
a Hack Day, there's someone doing
[00:01:36]
okay, I should, you know, remove
[00:01:38]
that old page which is still written
[00:01:40]
in React and Elmify
[00:01:42]
it. So it's so common that we
[00:01:44]
use the word Elmify. You know, I think
[00:01:46]
I remember a couple of years ago,
[00:01:48]
there was a large effort to change
[00:01:50]
like 10 of these pages and
[00:01:52]
the whole project was called, you know,
[00:01:54]
Elmifying, yada, yada, yada.
[00:01:56]
So...
[00:01:58]
We're all
[00:02:00]
trying to Elmify some small part
[00:02:02]
of the world. That's our...
[00:02:04]
Elmify people mostly.
[00:02:06]
Well...
[00:02:08]
That could be
[00:02:10]
dangerous. Be careful with that.
[00:02:12]
So,
[00:02:14]
so architecturally,
[00:02:16]
no red ink is not a single
[00:02:18]
page app, a single, single
[00:02:20]
page app. It's multiple.
[00:02:22]
It's embedded as elements
[00:02:24]
on each page, right? Yeah,
[00:02:26]
that's true. So I think
[00:02:28]
I should have said that as a
[00:02:30]
word of warning. Like everything
[00:02:32]
that I'm going to say today
[00:02:34]
is going to be very
[00:02:36]
strongly influenced by that
[00:02:38]
decision. And I'm sure that
[00:02:40]
there are applications out there which
[00:02:42]
are structured as one
[00:02:44]
whole single page app for
[00:02:46]
the whole product. And definitely
[00:02:48]
your experience might
[00:02:50]
be completely different.
[00:02:52]
In our case, I think even
[00:02:54]
at the beginning, we
[00:02:56]
decided to go through this approach
[00:02:58]
of creating an individual
[00:03:00]
endpoint for each page
[00:03:02]
so that we could more
[00:03:04]
easily experiment with
[00:03:06]
Elm at the beginning and
[00:03:08]
further later with different
[00:03:10]
approaches on how to write an Elm app.
[00:03:12]
Of course, nowadays,
[00:03:14]
many of these apps, they share some
[00:03:16]
code or some
[00:03:18]
components or some
[00:03:20]
sort of way of writing
[00:03:22]
Elm. But I think this structure
[00:03:24]
helps us a lot to
[00:03:26]
work at scale, right?
[00:03:28]
Right, right. So you
[00:03:30]
bypass some of the, I mean
[00:03:32]
it's a different type of scaling Elm
[00:03:34]
application than scaling a single
[00:03:36]
page app. You don't have the entire
[00:03:38]
routing layer and all those pieces are handled
[00:03:40]
by Rails or I believe
[00:03:42]
Haskell in some instances.
[00:03:44]
We use Haskell just as part
[00:03:46]
of microservices which don't
[00:03:48]
really serve Elm
[00:03:50]
apps. Well, I'm saying that
[00:03:52]
they serve very rarely used
[00:03:54]
Elm apps.
[00:03:56]
But the majority
[00:03:58]
of the routing is done
[00:04:00]
in Rails. That is not to
[00:04:02]
say that we do also have single page applications,
[00:04:04]
but they're just one of the many
[00:04:06]
Elm apps that we serve.
[00:04:08]
So we have some
[00:04:10]
individual pages where we thought that having
[00:04:12]
an SPA gives a much better
[00:04:14]
user experience. So those are built
[00:04:16]
using the SPA
[00:04:18]
framework.
[00:04:20]
The one by Ryan Haskell, guys?
[00:04:22]
No, no, no. So I think
[00:04:24]
when we started writing those,
[00:04:26]
the one that Ryan built
[00:04:28]
didn't exist yet.
[00:04:30]
Yeah, it's pretty recent, right?
[00:04:32]
So we have our own little
[00:04:34]
wrapper around
[00:04:36]
the one provided by
[00:04:38]
the Elm
[00:04:40]
package.
[00:04:42]
Right, I saw that in
[00:04:44]
your blog post. So we'll link to your
[00:04:46]
element no red ink blog post.
[00:04:48]
That's one of the details that I love
[00:04:50]
when no red ink people
[00:04:52]
share little tidbits from the
[00:04:54]
code base. I always
[00:04:56]
see Richard dropping little
[00:04:58]
helpers that seem really cool and you
[00:05:00]
have some in this post where you
[00:05:02]
wrap things and custom
[00:05:04]
tailor in a little way where instead
[00:05:06]
of browser.element you
[00:05:08]
kind of take in a flags decoder
[00:05:10]
and these little things that
[00:05:12]
just make it a little more ergonomic.
[00:05:14]
Yeah, I think that
[00:05:16]
makes particular sense
[00:05:18]
in a case where
[00:05:20]
there are going to be many people
[00:05:22]
doing that operation
[00:05:24]
many times. I feel
[00:05:26]
like if I'm working on my
[00:05:28]
own project, I wouldn't take the time to do that
[00:05:30]
because how
[00:05:32]
many L maps are you going to create?
[00:05:34]
One. Yeah, probably
[00:05:36]
one or maybe two or three.
[00:05:38]
But if you're in a large app
[00:05:40]
and especially with regards
[00:05:42]
to new engineers,
[00:05:44]
right, like you want to create a page
[00:05:46]
but you don't really know where to do
[00:05:48]
what to do. So having a script
[00:05:50]
that, for example, generates
[00:05:52]
all this boilerplate for you
[00:05:54]
it makes things so much easier.
[00:05:56]
And I think also having like some
[00:05:58]
little bits of structure
[00:06:00]
on top of the normal
[00:06:02]
program that Elm provides
[00:06:04]
you makes a lot of things easier
[00:06:06]
such as
[00:06:08]
tracking events or
[00:06:10]
being able to pass some more context
[00:06:12]
into our
[00:06:14]
applications for many
[00:06:16]
reasons. One example
[00:06:18]
I can think of, which I'm sure I mentioned
[00:06:20]
in the article as well, is
[00:06:22]
with regards to accessibility because
[00:06:24]
we want to detect
[00:06:26]
which is the main way
[00:06:28]
that the user is using the site
[00:06:30]
and to do so, we
[00:06:32]
have to track that in one way or the other
[00:06:34]
and having that in our program
[00:06:36]
makes it a solved
[00:06:38]
problem. You can just create a new endpoint
[00:06:40]
and you will have that information
[00:06:42]
with you.
[00:06:44]
I've been thinking about this
[00:06:46]
lately. I think that
[00:06:48]
create like frameworkizing
[00:06:50]
things, which is sort of what we're talking about
[00:06:52]
here, like creating little mini
[00:06:54]
custom tailored frameworks
[00:06:56]
and for your own purposes
[00:06:58]
to me it's almost like
[00:07:00]
a refactoring process
[00:07:02]
where you see these patterns
[00:07:04]
emerging where you're doing something
[00:07:06]
over and over and you just try to refactor
[00:07:08]
it. Has that
[00:07:10]
been your experience as you've seen these abstractions
[00:07:12]
emerge? Do you see duplication
[00:07:14]
emerging in all of these pages and then
[00:07:16]
you start to abstract it away?
[00:07:18]
I think that's definitely
[00:07:20]
one part of
[00:07:22]
the problem. I wouldn't
[00:07:24]
say that extracting duplication
[00:07:26]
has been our main concern.
[00:07:28]
I think our goal
[00:07:30]
was just to try to
[00:07:32]
make the big problems
[00:07:34]
easy or make the
[00:07:36]
hard problems easy. There are
[00:07:38]
some things which are usually a bit awkward
[00:07:40]
to set up. I think that
[00:07:42]
has been our main goal with creating these
[00:07:44]
frameworks is to do that.
[00:07:46]
I can still think of some little duplications
[00:07:48]
here and there that
[00:07:50]
we haven't really solved and
[00:07:52]
it's sort of okay
[00:07:54]
still. Even
[00:07:56]
with these little wrappers, I can
[00:07:58]
remember a few projects as
[00:08:00]
where we still experiment with new
[00:08:02]
ways of setting up
[00:08:04]
the program. For example, one of
[00:08:06]
the recent efforts was to
[00:08:08]
find a way to handle
[00:08:10]
models throughout the
[00:08:12]
whole frontend. Because right
[00:08:14]
now every individual program
[00:08:16]
has to handle their own
[00:08:18]
models, decide what to do when
[00:08:20]
they come in front or
[00:08:22]
when you dismiss them, where should the focus
[00:08:24]
return and things like that.
[00:08:26]
I think we're just trying to solve
[00:08:28]
these difficult problems and
[00:08:30]
figure out if there's an easy way so that
[00:08:32]
everybody can benefit from those
[00:08:34]
fixes.
[00:08:36]
It's more about finding the
[00:08:38]
ways that things could go wrong and
[00:08:40]
trying to create a path
[00:08:42]
where you don't have to think about those
[00:08:44]
everywhere.
[00:08:46]
We developed new things to help
[00:08:48]
remove boilerplate or ensure
[00:08:50]
consistency or make some hard things
[00:08:52]
easier and use that in
[00:08:54]
one of the pages or one
[00:08:56]
part of the project.
[00:08:58]
Since you have a large codebase,
[00:09:00]
what do you do to make that
[00:09:02]
use everywhere?
[00:09:04]
How do you spread that?
[00:09:06]
Hint, hint, nudge, nudge.
[00:09:08]
I wasn't even thinking of an answer.
[00:09:10]
I was just
[00:09:12]
plainly asking.
[00:09:14]
I see where you're going.
[00:09:16]
This is whether I'm going to disappoint
[00:09:18]
you. We don't use Unreview for
[00:09:20]
that.
[00:09:22]
We do have some scripts
[00:09:24]
where we write down deprecations.
[00:09:26]
I think this is especially
[00:09:28]
important for our
[00:09:30]
UI component
[00:09:32]
slash storybook
[00:09:34]
component,
[00:09:36]
which is called an Arriding UI.
[00:09:38]
Which is published?
[00:09:40]
It's published.
[00:09:42]
You can look it up.
[00:09:44]
From the repository,
[00:09:46]
we should also be able to reach
[00:09:48]
a Netlify page
[00:09:50]
where you can see the preview of all
[00:09:52]
those UI components.
[00:09:54]
I think that's quite a nice example
[00:09:56]
because all those components are
[00:09:58]
version. Usually when we
[00:10:00]
release a new version of
[00:10:02]
a model, we want to have a way
[00:10:04]
to keep track where in the app we're
[00:10:06]
using all the older versions.
[00:10:08]
We have this little Python script
[00:10:10]
that we run as part of our
[00:10:12]
CI suite that keeps
[00:10:14]
track of that and just writes
[00:10:16]
it down a file. I think
[00:10:18]
we used to have some rules like
[00:10:20]
this file can't get bigger than this or bigger
[00:10:22]
than that. But I think just having that
[00:10:24]
information somewhere is quite important
[00:10:26]
and to be able to
[00:10:28]
track where
[00:10:30]
are all these deprecated
[00:10:32]
usages of
[00:10:34]
these libraries. Something that I don't
[00:10:36]
think has been published yet,
[00:10:38]
but I think Aaron
[00:10:40]
has been working on it
[00:10:42]
on and off is a way to do
[00:10:44]
some of these upgrades in an automatic
[00:10:46]
fashion.
[00:10:48]
I'm not sure how much is available
[00:10:50]
on GitHub, but
[00:10:52]
I think one day we will see it
[00:10:54]
in its true vision and it's going
[00:10:56]
to be fantastic.
[00:10:58]
Looking forward to it.
[00:11:00]
When I asked the question,
[00:11:02]
I was also wondering,
[00:11:04]
I wasn't even thinking about Elm Review.
[00:11:06]
Although it does sound
[00:11:10]
something that Elm Review can help
[00:11:12]
with, definitely, even with the
[00:11:14]
Python script that you mentioned.
[00:11:16]
But I was thinking, how do you organize
[00:11:18]
it on a company level,
[00:11:20]
team based level?
[00:11:22]
Do you just go
[00:11:24]
hey everyone,
[00:11:26]
can you please all start
[00:11:28]
using this component this way or
[00:11:30]
start using this tool?
[00:11:32]
Or how do you do it?
[00:11:34]
I'm not sure if this
[00:11:36]
is a company wide
[00:11:38]
thing, but it's definitely something that
[00:11:40]
I truly believe in.
[00:11:42]
If you want to get a
[00:11:44]
change across a code base,
[00:11:46]
it has to be automated.
[00:11:48]
Especially in the
[00:11:50]
latest years, I've come to this
[00:11:52]
firm belief that if you want
[00:11:54]
to have something enforced,
[00:11:56]
you have to make it part of an
[00:11:58]
automated process.
[00:12:00]
At least partially, I'm guessing.
[00:12:02]
At least partially, what can be
[00:12:04]
done? But I think if it
[00:12:06]
can't be automated, then it should be okay
[00:12:08]
that it's not enforced.
[00:12:10]
I think I come
[00:12:12]
from a Ruby on Rails
[00:12:14]
sort of background. I think there's a lot in the
[00:12:16]
Ruby community, this idea of writing
[00:12:18]
beautiful code,
[00:12:20]
or doing things the right way.
[00:12:22]
But I don't think there's much focus
[00:12:24]
into finding a way to
[00:12:26]
make it
[00:12:28]
automated. I think in many
[00:12:30]
of these choices, we've
[00:12:32]
tried to come up with ways
[00:12:34]
to define these constraints
[00:12:36]
in code and make them part
[00:12:38]
of CI so that
[00:12:40]
everybody learns
[00:12:42]
through these
[00:12:44]
failing tests.
[00:12:46]
If someone really hates the
[00:12:48]
idea behind the rule, they
[00:12:50]
can make a case for it, disable the
[00:12:52]
rule. That's fine. But I think at least
[00:12:54]
it makes the conversation very
[00:12:56]
much practical.
[00:12:58]
You can look exactly what the code is doing
[00:13:00]
if there's something that you disagree with.
[00:13:02]
But I think you lose all this
[00:13:04]
white shedding about,
[00:13:06]
oh, but I think this is the right way to structure
[00:13:08]
something.
[00:13:10]
In my opinion, you have to accept
[00:13:12]
the differences then at that point
[00:13:14]
when you can't enforce it in any way.
[00:13:16]
Absolutely. Can't agree more.
[00:13:18]
How do you try to
[00:13:20]
automate these changes? What tools
[00:13:22]
do you have? Do you use scripts to
[00:13:24]
change your code? And how do you do that?
[00:13:26]
Do you use Elm syntax? Do you use
[00:13:28]
Elm language
[00:13:30]
tree sitter? What do you do?
[00:13:32]
If I think about the scripts we've used
[00:13:34]
over time, it's just a mishmash
[00:13:36]
of every possible thing that you can
[00:13:38]
think of, like simple batch scripts.
[00:13:40]
Programs that run in Python
[00:13:42]
that grab
[00:13:44]
through the Elm files to figure out
[00:13:46]
import trees dependencies
[00:13:48]
and shove them into
[00:13:50]
an SQLite database
[00:13:52]
to create these
[00:13:54]
recursive queries where you can figure
[00:13:56]
out the whole dependency trees.
[00:13:58]
I've personally written some Ruby scripts
[00:14:00]
because I couldn't find
[00:14:02]
something and I just did it.
[00:14:04]
So I think it doesn't really matter.
[00:14:06]
I'm sure that
[00:14:08]
Brian has done a lot of work
[00:14:10]
with tree sitter. So I'm sure that
[00:14:12]
now probably the same script is
[00:14:14]
using tree sitter nowadays.
[00:14:16]
But I think it's sort
[00:14:18]
of like an implementation detail
[00:14:20]
as long
[00:14:22]
as the script is there
[00:14:24]
even if it's a bit buggy, it's
[00:14:26]
fine. As long as the general
[00:14:28]
idea of that
[00:14:30]
these
[00:14:32]
constraints are enforced.
[00:14:34]
Are these more flagging inconsistencies
[00:14:36]
or
[00:14:38]
making things consistent
[00:14:40]
or a mixture of both?
[00:14:42]
How to say a mixture
[00:14:44]
of both? I think maybe now
[00:14:46]
it's not like
[00:14:48]
I don't want to give this impression
[00:14:50]
that we have thousands of scripts, but I feel
[00:14:52]
right now we might be running three
[00:14:54]
or four and one of them is Elm review.
[00:14:56]
So maybe we will
[00:14:58]
talk throughout the episode
[00:15:00]
maybe later a bit more
[00:15:02]
in detail about how we
[00:15:04]
adopted Elm review in the code base.
[00:15:08]
But I think
[00:15:10]
some of the things that we're going to talk about
[00:15:12]
they're also not enforced.
[00:15:14]
But I think that's
[00:15:16]
the point I was trying to make that
[00:15:18]
since they're not enforced through
[00:15:20]
the code, maybe
[00:15:22]
we have this tacit
[00:15:24]
and shared understanding
[00:15:26]
that this is the right way to do it,
[00:15:28]
but we're also okay with
[00:15:30]
other ways of structuring the code.
[00:15:32]
I don't think you'd ever
[00:15:34]
encounter in a code review
[00:15:36]
saying, oh, don't structure the code in this
[00:15:38]
way.
[00:15:40]
I feel like that's what we've
[00:15:42]
sort of agreed upon that
[00:15:44]
it's basically fine
[00:15:46]
since that we have the separated Elm
[00:15:48]
apps also to write code in a
[00:15:50]
slightly different way. And also it's
[00:15:52]
just like the same nature of a very large
[00:15:54]
application is that
[00:15:56]
every once in a while you will bump into
[00:15:58]
an Elm app that nobody has touched
[00:16:00]
for a year.
[00:16:02]
And everything that now
[00:16:04]
we think is the right way to
[00:16:06]
write Elm apps wasn't
[00:16:08]
at the time. So
[00:16:10]
I think it's just like a common
[00:16:12]
to be able to accept
[00:16:14]
diversity in the code base
[00:16:16]
to a level where
[00:16:18]
all the types are passing,
[00:16:20]
all the tests are passing.
[00:16:22]
I think there's some
[00:16:24]
things that we cannot let go
[00:16:26]
off. But apart from that,
[00:16:28]
I think we are pretty
[00:16:30]
accepting of different
[00:16:32]
ways of writing Elm.
[00:16:34]
Yeah, the most important thing is it
[00:16:36]
works.
[00:16:38]
And to a certain extent,
[00:16:40]
without diversity and
[00:16:42]
experimentation, you don't grow and
[00:16:44]
learn new techniques. So
[00:16:46]
I could imagine that's part of the
[00:16:48]
secret sauce of stumbling upon
[00:16:50]
these techniques that are really effective
[00:16:52]
over time. And I think also
[00:16:54]
you end up going back and forth
[00:16:56]
on beliefs
[00:16:58]
and ideas. Like every once
[00:17:00]
in a while you think, okay, I need
[00:17:02]
to write a library and I need a way to
[00:17:04]
pass options to this library.
[00:17:06]
And I really like the Elm HTML
[00:17:08]
design where you pass
[00:17:10]
this list of attributes.
[00:17:12]
But every once in a while
[00:17:14]
you bump into the other issue that,
[00:17:16]
oh, but what should I do
[00:17:18]
when someone passes the same option twice
[00:17:20]
or they pass them
[00:17:22]
duplicated? And then you
[00:17:24]
think, okay, I'm going to pass
[00:17:26]
a map there.
[00:17:28]
It feels to me like every once
[00:17:30]
in a while you think, okay, this is the best
[00:17:32]
solution. And then you think about another
[00:17:34]
slightly different application and
[00:17:36]
your best solution is a bit tricky
[00:17:38]
and you go back to the other one.
[00:17:40]
So I feel even in
[00:17:42]
some of our UI components, you can
[00:17:44]
still see this. Like if you look at the
[00:17:46]
shape of the API, some of them
[00:17:48]
they really love this Elm HTML
[00:17:50]
pattern. Some of them
[00:17:52]
really like this option of creating
[00:17:54]
a sort of option
[00:17:56]
map. And then you have this final call
[00:17:58]
which instantiates the thing
[00:18:00]
or renders the checkbox
[00:18:02]
where you do
[00:18:04]
this pipeline sort of syntax.
[00:18:06]
I like the builder pattern.
[00:18:08]
Yeah, the builder pattern. Sorry, I couldn't remember
[00:18:10]
the name.
[00:18:12]
Yeah, I mean in API design
[00:18:14]
Jeroen and I have talked about this a lot, but you
[00:18:16]
have to have all those tools
[00:18:18]
at your disposal because it's really about
[00:18:20]
modeling
[00:18:22]
different solutions to a problem. And the
[00:18:24]
only way to really do that is to try a
[00:18:26]
lot of things and really
[00:18:28]
deeply think about the problem and
[00:18:30]
the needs of your particular
[00:18:32]
domain can change over time.
[00:18:34]
There's personal preference
[00:18:36]
in there when there are different ways to do things
[00:18:38]
to achieve the same solutions
[00:18:40]
and get the same constraints.
[00:18:42]
So it's really interesting
[00:18:44]
how that stuff evolves
[00:18:46]
within the context of a company's
[00:18:48]
code base. So what are some
[00:18:50]
of the things that
[00:18:52]
you think have been really important
[00:18:54]
for helping keep a large
[00:18:56]
code base maintainable?
[00:18:58]
What have been some of those techniques that you think
[00:19:00]
have helped you? Maybe the
[00:19:02]
easiest answer is the Elm
[00:19:04]
compiler. I feel like that's the
[00:19:06]
major helper, right?
[00:19:08]
Especially when you're making a change
[00:19:10]
to a data structure
[00:19:12]
or to a component that's very
[00:19:14]
widely used, or you need
[00:19:16]
to reshape it in some slight
[00:19:18]
different way. Maybe
[00:19:20]
when you wrote that component
[00:19:22]
initially, it didn't support
[00:19:24]
keyboard
[00:19:26]
usage, right? And now it needs to support
[00:19:28]
that. So the surrounding app
[00:19:30]
needs to react
[00:19:32]
to all these different things
[00:19:34]
that the component now can do.
[00:19:36]
Having the Elm compiler
[00:19:38]
just gives you this
[00:19:40]
confidence that you can sleep
[00:19:42]
at night, right?
[00:19:44]
You make the change, and it just applies
[00:19:46]
to all the different Elm endpoints
[00:19:48]
that we have. And
[00:19:50]
eventually there will be a time
[00:19:52]
later in the day where
[00:19:54]
the tests are passing
[00:19:56]
and the types are compiling, and
[00:19:58]
then that's done. I'd say
[00:20:00]
that's the major thing. And
[00:20:02]
especially if
[00:20:04]
I think about our choice to write
[00:20:06]
more Haskell, I think that was definitely
[00:20:08]
inspired
[00:20:10]
by the difference in
[00:20:12]
developer experience when
[00:20:14]
refactoring Elm and
[00:20:16]
refactoring Ruby. Just like this
[00:20:18]
idea that you can make this change
[00:20:20]
across 40 files,
[00:20:22]
and the change is sound,
[00:20:24]
and the change is solid. So I think
[00:20:26]
in that perspective, maybe there's
[00:20:28]
not a true difference between a large code
[00:20:30]
base and a small code base in Elm.
[00:20:32]
At least that's why I was
[00:20:34]
thinking, I think
[00:20:36]
yesterday I was mulling
[00:20:38]
over some of the questions about
[00:20:40]
what does it mean to work in a large code
[00:20:42]
base? And truly
[00:20:44]
now I can't really think
[00:20:46]
what's the difference really. There
[00:20:48]
are some differences, but
[00:20:50]
at the end of the day, the real
[00:20:52]
thing is that a compiler
[00:20:54]
is going to go through all your code
[00:20:56]
and make sure that all these
[00:20:58]
little constraints that you've sprinkled across
[00:21:00]
the years in the code base,
[00:21:02]
all of them, they're all
[00:21:04]
true.
[00:21:06]
I think it's also this power of
[00:21:08]
this compound
[00:21:10]
interest. That
[00:21:12]
little assertion, that little
[00:21:14]
constraint you wrote seven years ago
[00:21:16]
has been
[00:21:18]
enforcing that thing since
[00:21:20]
then. And all the new ones that
[00:21:22]
you've written today are going to
[00:21:24]
have this compound effect in five years.
[00:21:26]
So it's like this difference
[00:21:28]
between when you invest
[00:21:30]
1% of your pension
[00:21:32]
when you're 15 years old,
[00:21:34]
sorry, when you're 18 years old,
[00:21:36]
as opposed to investing
[00:21:38]
20% of your
[00:21:40]
income when you're 50
[00:21:42]
years old. The power of
[00:21:44]
compound interest is just
[00:21:46]
mind boggling. I love that idea
[00:21:48]
that you don't see a clear line
[00:21:50]
between what it's like managing
[00:21:52]
a large or a small Elm code base.
[00:21:54]
Because I do think that that speaks
[00:21:56]
to what's really
[00:21:58]
fascinating and unique
[00:22:00]
about Elm is that it
[00:22:02]
is, it's designed in a way
[00:22:04]
where you have to deal with all these
[00:22:06]
cases explicitly. You have to model
[00:22:08]
these constraints explicitly. You have to,
[00:22:10]
it doesn't let you get away with
[00:22:12]
not handling something
[00:22:14]
or not being explicit about something or
[00:22:16]
not being precise about something. But then
[00:22:18]
the benefit is you're working with
[00:22:20]
a giant code base and it doesn't feel any different
[00:22:22]
than if you're working with a small code base.
[00:22:24]
Which is one of the reasons why it's difficult
[00:22:26]
to kind of share
[00:22:28]
the joy of Elm with people because
[00:22:30]
you show people a to do list application
[00:22:32]
and they're like, okay, well, that's not
[00:22:34]
that special. But you're like, no, but it's actually
[00:22:36]
like, go make a change in a large
[00:22:38]
code base and see
[00:22:40]
how the compiler walks you through your changes.
[00:22:42]
You still get that same experience.
[00:22:44]
Yeah, I think that's like
[00:22:46]
one of the things, I'm not sure if Richard
[00:22:48]
has spoken
[00:22:50]
about it publicly,
[00:22:52]
but it's like one of those things that
[00:22:54]
you can only experience when you try
[00:22:56]
it.
[00:22:58]
And it doesn't really, you know, like you can
[00:23:00]
hear it or you can read about it,
[00:23:02]
but up to the moment where
[00:23:04]
you try it and you see it, you
[00:23:06]
just can't believe it. So I think
[00:23:08]
that's what makes it difficult. Like it's difficult
[00:23:10]
to say, well, that same feeling
[00:23:12]
that you get here, you're going to have it
[00:23:14]
in a large code base. And I think
[00:23:16]
you know, even before I joined the company, I had
[00:23:18]
the same curiosity.
[00:23:20]
Like, well, but how does it feel to
[00:23:22]
write Elm at a
[00:23:24]
large scale? And people
[00:23:26]
used to say, well, we just do it.
[00:23:28]
I think maybe that's the answer.
[00:23:30]
Like when you're working on
[00:23:32]
a daily basis, it doesn't really feel
[00:23:34]
different from anything else.
[00:23:36]
You just run Elm test.
[00:23:38]
Like now in our code base probably
[00:23:40]
has to run 5000 tests.
[00:23:42]
It takes a minute. But when that's
[00:23:44]
done, you know that everything
[00:23:46]
is connected. All those little
[00:23:48]
types are connected in the right
[00:23:50]
way. Not the right data are
[00:23:52]
flowing through those little pipes.
[00:23:54]
And once that's
[00:23:56]
done, you just take it and push it
[00:23:58]
and you know, go on
[00:24:00]
with your day. Right.
[00:24:02]
I think one of the nice things is that
[00:24:04]
there's a lot of work that you don't have to do.
[00:24:06]
So the compiler
[00:24:08]
makes sure that you do all the necessary
[00:24:10]
work, but it doesn't ask you to do
[00:24:12]
more. So
[00:24:14]
when I was doing JavaScript in a pretty
[00:24:16]
large code base, I can
[00:24:18]
imagine that I was doing some
[00:24:20]
refactoring and then the
[00:24:22]
value that I computed somewhere was being
[00:24:24]
passed to a very
[00:24:26]
old, very complex module.
[00:24:28]
And I would have to look
[00:24:30]
at the implementation
[00:24:32]
to make sure that my changes
[00:24:34]
did not break whatever it was doing
[00:24:36]
inside there. But with the compiler
[00:24:38]
I don't have to do that.
[00:24:40]
I just know if the compiler is
[00:24:42]
still happy, I'm happy
[00:24:44]
and we can move on. And I don't have to
[00:24:46]
look at that old and complex module.
[00:24:48]
So that is very refreshing.
[00:24:50]
I think this is extremely important
[00:24:52]
also because of other
[00:24:54]
reasons that might not be evident
[00:24:56]
at first. For example
[00:24:58]
in our case, we work in distributed
[00:25:00]
teams. So you
[00:25:02]
might make a change which introduces
[00:25:04]
14 compiler errors
[00:25:06]
and it's
[00:25:08]
at the end of your day and
[00:25:10]
you don't want to be working late because
[00:25:12]
you have to go out
[00:25:14]
and eat a pizza or something.
[00:25:18]
I think it's just such a good way
[00:25:20]
to leave work for someone else saying
[00:25:22]
okay, I've been going through in the middle of this change
[00:25:24]
but here are all the places
[00:25:26]
where the build is broken.
[00:25:28]
If you have time, can you take a look and
[00:25:30]
fix all the compiler errors?
[00:25:32]
And I feel it's just
[00:25:34]
such a good way also to collaborate
[00:25:36]
with other people.
[00:25:38]
I think that's what I want
[00:25:40]
to stress in this very practical
[00:25:42]
way. It's not like you have to
[00:25:44]
write a very long message to try
[00:25:46]
to explain
[00:25:48]
the purpose of your
[00:25:50]
grand change. I think that
[00:25:52]
happens a lot in other languages
[00:25:54]
where you have to have
[00:25:56]
really to communicate your big
[00:25:58]
plan before someone else can
[00:26:00]
take on your work.
[00:26:02]
I feel that in Elm,
[00:26:04]
if you made the
[00:26:06]
problematic change that
[00:26:08]
is breaking the code, then fixing
[00:26:10]
that becomes a sort of
[00:26:12]
different task.
[00:26:14]
It's almost mechanical.
[00:26:16]
Of course in some places
[00:26:18]
you have to look at the code
[00:26:20]
a little bit, see how the
[00:26:22]
pieces of the puzzle fit.
[00:26:24]
But I feel that it becomes almost something
[00:26:26]
that's already done.
[00:26:28]
It's just
[00:26:30]
already done in
[00:26:32]
theory. In practice, it's not done.
[00:26:34]
But the big part of the
[00:26:36]
plan has already been done.
[00:26:38]
The scary part is done. Knowing what to change
[00:26:40]
and knowing what to watch
[00:26:42]
out for, that's the
[00:26:44]
compiler's job. A word that's coming up for
[00:26:46]
me is contracts because often
[00:26:48]
teams will have to negotiate the
[00:26:50]
contract of an API
[00:26:52]
or like a
[00:26:54]
public facing REST API
[00:26:56]
or whatever or
[00:26:58]
how different interfaces fit
[00:27:00]
together. When you have
[00:27:02]
different teams, they have to figure
[00:27:04]
out those contracts between these pieces
[00:27:06]
that interconnect.
[00:27:08]
Elm is so explicit about contracts.
[00:27:10]
Whether it's communicating
[00:27:12]
with a server or
[00:27:14]
interfacing with a
[00:27:16]
common shared piece of code,
[00:27:18]
the contracts are so explicit.
[00:27:20]
When negotiating those things between
[00:27:22]
teams, it makes it
[00:27:24]
a lot easier because you have this explicit
[00:27:26]
definition of these contracts
[00:27:28]
everywhere you go. Maybe we can
[00:27:30]
talk a little bit about the testing story
[00:27:32]
at No Red Ink.
[00:27:34]
You mentioned I think 5,000
[00:27:36]
tests and
[00:27:38]
in this blog post you have about
[00:27:40]
Elm at No Red Ink,
[00:27:42]
you talk about some of these details.
[00:27:44]
Maybe you could walk us through a little
[00:27:46]
bit what it looks like if you sit down to
[00:27:48]
implement a new feature.
[00:27:50]
What does the testing look like for that?
[00:27:52]
That's a great question.
[00:27:54]
I love to talk about testing.
[00:27:56]
I think in general, a lot of people
[00:27:58]
at No Red Ink
[00:28:00]
have done Ruby.
[00:28:02]
I think in the Ruby world,
[00:28:04]
there's this almost obsessive
[00:28:06]
love for testing.
[00:28:08]
The most common
[00:28:10]
Ruby library is probably RSVAC
[00:28:12]
or assertions
[00:28:14]
and stuff.
[00:28:16]
It's just like the bread and butter of
[00:28:18]
a Rubyist.
[00:28:20]
I think that's the reason
[00:28:22]
why in NRI
[00:28:24]
we do a lot of testing
[00:28:26]
even in a place where I think
[00:28:28]
a lot of people when they think of you have
[00:28:30]
static types, therefore you don't need
[00:28:32]
testing.
[00:28:34]
I think we just love tests so much that we just
[00:28:36]
cannot not write them.
[00:28:40]
We always start from
[00:28:42]
what we call an acceptance test.
[00:28:44]
This is usually written in Ruby.
[00:28:46]
It uses this library
[00:28:48]
which drives a real browser.
[00:28:50]
Right now we're using Firefox.
[00:28:52]
It spawns a Firefox instance,
[00:28:54]
opens a copy of the Rails
[00:28:56]
application, and tries to
[00:28:58]
click, submit forms,
[00:29:00]
do everything, run assertions on the page.
[00:29:02]
I think usually that's almost
[00:29:04]
at the level of the user
[00:29:06]
starting. But of course these tests
[00:29:08]
are really slow because they need to spawn
[00:29:10]
an actual browser instance.
[00:29:12]
We try to keep those tests
[00:29:14]
to only the happy path.
[00:29:16]
Basically we don't handle errors,
[00:29:18]
we don't handle possible problems.
[00:29:22]
We just do everything exactly
[00:29:24]
that will lead to a good outcome.
[00:29:26]
We will assert that the page says
[00:29:28]
your assignment has been created,
[00:29:30]
and then our work is done.
[00:29:32]
This is the general flow. Sometimes we will
[00:29:34]
also write some regression tests
[00:29:36]
in this fashion if there's some
[00:29:38]
really critical user experience
[00:29:40]
flows.
[00:29:42]
Especially I think the moment
[00:29:44]
where you figure out a very nasty
[00:29:46]
bug in all of these pages which is
[00:29:48]
causing a lot of user pain,
[00:29:50]
we like to write a lot of tests
[00:29:52]
just to cover that to make sure that it never happens again.
[00:29:54]
I think this is the
[00:29:56]
highest level of testing
[00:29:58]
that I can think of.
[00:30:00]
Actually that's not true. We even have
[00:30:02]
Cypress tests which we run
[00:30:04]
against a live staging
[00:30:06]
instance of the application.
[00:30:08]
Interesting.
[00:30:10]
Is there a reason that you use
[00:30:12]
I'm guessing like Capybara or something on the Ruby side and then
[00:30:14]
you have to do a lot of testing
[00:30:16]
on the Ruby side?
[00:30:18]
Yeah, I think that's a good question.
[00:30:20]
I think that we use something on the Ruby side
[00:30:22]
and then Cypress
[00:30:24]
for the other acceptance tests.
[00:30:26]
Is there a reason that you have the split
[00:30:28]
or if you were doing it fresh today
[00:30:30]
would you maybe choose Cypress for
[00:30:32]
all of them?
[00:30:34]
Yeah, that's a good question. I feel that
[00:30:36]
I really love Cypress
[00:30:38]
as a tool, but I feel that it's even
[00:30:40]
more flaky than Capybara
[00:30:42]
because
[00:30:44]
it also depends on
[00:30:46]
the fact that that piece of
[00:30:48]
structure is always there.
[00:30:50]
I think there is some value in
[00:30:52]
having Capybara specs
[00:30:54]
because for example we can
[00:30:56]
very easily parallelize
[00:30:58]
Capybara specs. We can create
[00:31:00]
30 databases and
[00:31:02]
split the test suite across
[00:31:04]
30 groups and run them
[00:31:06]
concurrently. But we can't create
[00:31:08]
30 exact copies
[00:31:10]
of the staging instance.
[00:31:12]
I mean we could, but it would be
[00:31:14]
more difficult and time consuming
[00:31:16]
to do so. I still see
[00:31:18]
there are some benefits in having
[00:31:20]
this
[00:31:22]
ephemeral database
[00:31:24]
that you just run tests on and
[00:31:26]
you can control how many browser instances
[00:31:28]
you want to spawn.
[00:31:30]
Right, it's more connected to the actual
[00:31:32]
Rails app so you can do these
[00:31:34]
fine grained controls as you run these.
[00:31:36]
And also stub some things, right?
[00:31:38]
In some cases you don't really want to
[00:31:40]
make all those calls to that certain
[00:31:42]
API because you pay
[00:31:44]
for them or you're rate limited.
[00:31:46]
So having
[00:31:48]
the ability there to say do everything
[00:31:50]
for real except for this thing
[00:31:52]
is quite useful.
[00:31:54]
Do you stub or actually perform
[00:31:56]
queries to GraphQL
[00:31:58]
APIs in the process? Because that's
[00:32:00]
one pain point a lot of people run into is
[00:32:02]
how to do end to end
[00:32:04]
tests against a GraphQL API.
[00:32:06]
I think in our tests we just run them
[00:32:08]
so they just hit the end point and
[00:32:10]
the end point just comes back with the result.
[00:32:12]
So we don't do...
[00:32:14]
I think in that perspective we try to stub
[00:32:16]
as little as possible. In some cases
[00:32:18]
this cannot be avoided
[00:32:20]
but in this level of
[00:32:22]
testing we try to keep it really
[00:32:24]
to the least amount
[00:32:26]
of interference that you
[00:32:28]
can.
[00:32:30]
Yep, as realistic as possible
[00:32:32]
as you go up the
[00:32:34]
testing pyramid. I noticed in your
[00:32:36]
post about
[00:32:38]
element.no.redinc that you mentioned
[00:32:40]
view tests and I'm curious to
[00:32:42]
hear your perspective on that because
[00:32:44]
personally I haven't
[00:32:46]
found a use case where I've
[00:32:48]
personally found value with view tests
[00:32:50]
but it seems like you do
[00:32:52]
find value so I'd be really curious to hear
[00:32:54]
what you think is valuable about that.
[00:32:56]
Let me just quickly backtrack
[00:32:58]
on that. So I think we talked about
[00:33:00]
this very high level testing.
[00:33:02]
Yes, yes.
[00:33:04]
Down this pyramid I would say immediately
[00:33:06]
below that I would
[00:33:08]
have what we call the Elm
[00:33:10]
program test. I think that's
[00:33:12]
the right way to call them nowadays
[00:33:14]
which is the library
[00:33:16]
that Aaron has been
[00:33:18]
working on where you can basically
[00:33:20]
set up the Elm application.
[00:33:22]
You can assert
[00:33:24]
that certain commands,
[00:33:26]
well certain effects have been
[00:33:28]
produced. You can
[00:33:30]
simulate the response of the
[00:33:32]
API by providing some
[00:33:34]
sort of response and I feel like that
[00:33:36]
in my mind is the
[00:33:38]
level immediately
[00:33:40]
below the Rails test.
[00:33:42]
This is a test written in Elm.
[00:33:44]
It's much more reliable.
[00:33:46]
It's much faster but you have
[00:33:48]
to sort of connect the real app.
[00:33:50]
Usually in our
[00:33:52]
applications we pass some JSON
[00:33:54]
so that the page initializes with some
[00:33:56]
data. That's the right moment
[00:33:58]
for us to also search
[00:34:00]
that for example
[00:34:02]
that JSON that we pass
[00:34:04]
can be decoded
[00:34:06]
by the Elm application.
[00:34:08]
For example, I'm not sure if I talk
[00:34:10]
about this in the blog post but we have
[00:34:12]
a little bit of code that from
[00:34:14]
our Rails controllers automatically
[00:34:16]
generates JSON blocks
[00:34:18]
and we pass them to these Elm
[00:34:20]
program tests and they try to
[00:34:22]
decode it as a first thing.
[00:34:24]
Just to ensure the sanity that
[00:34:26]
if we make a big change to a controller
[00:34:28]
which breaks Elm pages
[00:34:30]
we notice it very quickly
[00:34:32]
because I think that's also something else
[00:34:34]
I'm sorry I'm talking too fast
[00:34:36]
but Ruby tests are
[00:34:38]
slow so there's no way you're going
[00:34:40]
to be able to run them
[00:34:42]
on your machine at every change
[00:34:44]
that you make especially
[00:34:46]
if you're working on your own branch
[00:34:48]
but Elm tests are fast
[00:34:50]
which means that if I'm just going
[00:34:52]
for a glass of water
[00:34:54]
or going for a little walk I just run
[00:34:56]
the whole Elm suite
[00:34:58]
it will finish in
[00:35:00]
50 seconds so by the time
[00:35:02]
I'm back from the restroom I can see
[00:35:04]
if I've broken anything
[00:35:06]
across the system.
[00:35:08]
So this is the second level
[00:35:10]
the Elm program test
[00:35:12]
and I would say below that
[00:35:14]
I would see the view test
[00:35:16]
where I think
[00:35:18]
the reason why we like those
[00:35:20]
is when you have very
[00:35:22]
large views
[00:35:24]
with a lot of conditionals
[00:35:26]
and inside the Elm program
[00:35:28]
test I feel it's a bit awkward
[00:35:30]
to be able to simulate all the possible
[00:35:32]
cases of these conditionals
[00:35:34]
you have to click on this button
[00:35:36]
then click on the other one then
[00:35:38]
close this model then do that
[00:35:40]
and instead in the view test you can just pass
[00:35:42]
a model and you can even
[00:35:44]
fuzz the model, right? You can
[00:35:46]
create a property
[00:35:48]
that generates thousands of
[00:35:50]
these models but you want to make sure that
[00:35:52]
no matter how the model is created
[00:35:54]
inside the view we can see
[00:35:56]
this and we can see that.
[00:35:58]
I see. That's a great description
[00:36:00]
thanks for that.
[00:36:02]
So like if you had like
[00:36:04]
an avatar for a user
[00:36:06]
and if it's a guest user or
[00:36:08]
a logged in user or an admin
[00:36:10]
you would have it displayed differently
[00:36:12]
you could test that and say
[00:36:14]
if the user is logged in
[00:36:16]
we see their name somewhere
[00:36:18]
and get confidence on
[00:36:20]
just that piece and have a more focused
[00:36:22]
test where you don't have to run through
[00:36:24]
all of the update loops to arrive
[00:36:26]
there. Yeah and I think
[00:36:28]
even below the view test
[00:36:30]
we would have what we normally call a unit
[00:36:32]
test, right? And I think we reserve
[00:36:34]
those for the really
[00:36:36]
algorithmic bits
[00:36:38]
you know like we have this pretty
[00:36:40]
large and convoluted algorithm
[00:36:42]
that does a lot of stuff
[00:36:44]
and operates with these four data
[00:36:46]
structures and does some
[00:36:48]
really interesting bits
[00:36:50]
I think that's where you write
[00:36:52]
unit tests because it's mostly
[00:36:54]
just purely, well
[00:36:56]
you could argue that even view tests are
[00:36:58]
pure tests, right? They don't
[00:37:00]
even actually write them
[00:37:02]
but I think that's the thing
[00:37:04]
the more you go down into this
[00:37:06]
pyramid the
[00:37:08]
faster tests get, better
[00:37:10]
you can describe all the
[00:37:12]
possible edge cases
[00:37:14]
but you're always like exercising
[00:37:16]
the test on a smaller
[00:37:18]
surface. Now the further down
[00:37:20]
you go, you're just
[00:37:22]
testing
[00:37:24]
a pin, the edge
[00:37:26]
of a pin at some point
[00:37:28]
while when you think
[00:37:30]
all the way to the top, the Cypress test
[00:37:32]
you're actually even testing
[00:37:34]
the network connection
[00:37:36]
to the instance that sits
[00:37:38]
somewhere in AWS.
[00:37:40]
Yeah, that makes a lot of sense.
[00:37:42]
So for Elm program tests
[00:37:44]
has it been challenging to make everything
[00:37:46]
use the effect type in the context of
[00:37:48]
No Red Ink or was that
[00:37:50]
pretty natural? It was pretty
[00:37:52]
natural. I feel like in some pages
[00:37:54]
we start using effects before
[00:37:56]
we start using Elm program tests.
[00:37:58]
Okay. Yeah. Because of testing
[00:38:00]
still or for other reasons?
[00:38:02]
I think maybe for a cultural
[00:38:04]
reason because in order to
[00:38:06]
write these wrappers
[00:38:08]
to the Elm programs
[00:38:10]
the thing that we discussed at the beginning of the episode
[00:38:12]
in order to do that
[00:38:14]
to write a program that wraps
[00:38:16]
the Elm program, you have
[00:38:18]
already to do that. You have to create
[00:38:20]
your own effect because
[00:38:22]
there are some of these side effects
[00:38:24]
that you want to propagate
[00:38:26]
down to the actual app
[00:38:28]
and then there are some events that you want
[00:38:30]
to keep at the level of your wrapper.
[00:38:32]
So for example, let's say
[00:38:34]
you want to keep track if the last
[00:38:36]
action done by the user was
[00:38:38]
a mouse click or a keyboard
[00:38:40]
press. That event is not
[00:38:42]
going to be propagated to the user
[00:38:44]
application. It's just going to
[00:38:46]
remain at the level of the wrapper.
[00:38:48]
And if you want to do that, you
[00:38:50]
have to be able to separate
[00:38:52]
what is a generic
[00:38:54]
effect that you want to handle
[00:38:56]
inside your wrapper or what is a user
[00:38:58]
effect that you want to propagate down to
[00:39:00]
the user code. So I feel like that's
[00:39:02]
just like something that the moment
[00:39:04]
you get a little bit curious and you try to
[00:39:06]
say, okay, I want to write something like that.
[00:39:08]
How does it work? And you try to write one
[00:39:10]
and maybe it takes you a couple of hours.
[00:39:12]
But from the moment you do that,
[00:39:14]
you realize that
[00:39:16]
it doesn't cost you much.
[00:39:18]
You just need to have a little function
[00:39:20]
and you can call it perform.
[00:39:22]
It goes from an effect
[00:39:24]
to a command message
[00:39:26]
and that's pretty much
[00:39:28]
it. And you can write
[00:39:30]
your own batching
[00:39:32]
logic or sequencing logic
[00:39:34]
of these effects. You can have complete
[00:39:36]
control over what
[00:39:38]
to do. I think sometimes on
[00:39:40]
the Elm Slack, I read people saying,
[00:39:42]
oh, I have many of these
[00:39:44]
commands and I want them to behave
[00:39:46]
in this certain way, but I don't know how to
[00:39:48]
do that. And I think
[00:39:50]
the moment you do that, you separate
[00:39:52]
the side effect, that command
[00:39:54]
message from your own effect.
[00:39:56]
You realize you basically have complete control
[00:39:58]
over what you want to do with that.
[00:40:00]
Fascinating. Yeah, that's a great
[00:40:02]
take on that.
[00:40:04]
Yeah, it really does
[00:40:06]
give you a lot more
[00:40:08]
control as a, I mean,
[00:40:10]
insofar as you're
[00:40:12]
able to be a sort of framework
[00:40:14]
author and you can sit,
[00:40:16]
I mean, I often think about this sort
[00:40:18]
of like, I think we can build our own
[00:40:20]
frameworks in Elm and I think people sometimes
[00:40:22]
don't realize the power
[00:40:24]
that we have available if
[00:40:26]
we create our own sort of wrappers around
[00:40:28]
things like these effect
[00:40:30]
types that we can get
[00:40:32]
fine grained control. You can
[00:40:34]
wrap browser.element
[00:40:36]
or browser.application. There are all
[00:40:38]
these things you can do
[00:40:40]
if you have a wrapper layer, you can even
[00:40:42]
have sort of a wrapped
[00:40:44]
state that you can control
[00:40:46]
as a sort of layer of abstraction
[00:40:48]
around the page state,
[00:40:50]
for example, which I believe
[00:40:52]
you don't explicitly say, but you
[00:40:54]
hint at in the post where you talk about
[00:40:56]
having this user session and passing
[00:40:58]
that in. This is sort of this wrapped
[00:41:00]
state. So you build your
[00:41:02]
own framework for your custom
[00:41:04]
needs. Okay, so
[00:41:06]
another big question that comes
[00:41:08]
up a lot in the context of larger
[00:41:10]
Elm applications would be
[00:41:12]
managing nested
[00:41:14]
state. This is like one of the really
[00:41:16]
big questions you touch on this
[00:41:18]
in your blog post. So
[00:41:20]
I guess there are two main pieces to
[00:41:22]
this. There's sort of web components
[00:41:24]
and then there's sort of
[00:41:26]
nested Elm applications.
[00:41:28]
What's kind of your
[00:41:30]
take on that? How
[00:41:32]
do you approach that at Neuridink?
[00:41:34]
I think this is a really great question
[00:41:36]
because when I wrote the blog post originally,
[00:41:38]
I feel a lot of people
[00:41:40]
around the company, they have different ideas
[00:41:42]
on what to do. I think
[00:41:44]
that most of the feedback I've
[00:41:46]
heard internally about
[00:41:48]
the blog post was exactly about that
[00:41:50]
section. So I think a lot of
[00:41:52]
people with their suggestion
[00:41:54]
would be don't nest state
[00:41:56]
at all. Try to create a component that
[00:41:58]
doesn't need the state in the first place.
[00:42:00]
And I think Evan
[00:42:02]
at the time, he wrote this
[00:42:04]
sortable table component
[00:42:06]
that I'm sure is still available
[00:42:08]
that you can check out on how to
[00:42:10]
design
[00:42:12]
a view which is complex
[00:42:14]
and allows for customization
[00:42:16]
without the need of storing state
[00:42:18]
anywhere. I think that's like
[00:42:20]
one quite brilliant
[00:42:22]
piece of design. I think personally
[00:42:24]
I'm more in the camp
[00:42:26]
of nest
[00:42:28]
applications. It's fine
[00:42:30]
if you feel like there's a necessity
[00:42:32]
to create a new type of message.
[00:42:34]
You can map
[00:42:36]
HTML. You can
[00:42:38]
handle the new message.
[00:42:40]
Do something with it. I feel like
[00:42:42]
if you do it once,
[00:42:44]
you pretty much understand
[00:42:46]
what the complexity is.
[00:42:48]
I feel like in a lot
[00:42:50]
of places we say, oh,
[00:42:52]
this is too heavy handed
[00:42:54]
and it requires like a lot
[00:42:56]
of glue code, but I don't feel
[00:42:58]
it's that true. Maybe I've just
[00:43:00]
taken the
[00:43:02]
red pill so I just can't see
[00:43:04]
how awkward
[00:43:06]
it is. But personally,
[00:43:08]
I'm in the camp of nest
[00:43:10]
applications where you think
[00:43:12]
it makes sense.
[00:43:14]
Definitely the first time you do it, you have
[00:43:16]
to probably use HTML
[00:43:18]
map, which is something you
[00:43:20]
might have never used before.
[00:43:22]
You're maybe going to spend
[00:43:24]
a few minutes to figure out what does it
[00:43:26]
mean to write HTML map?
[00:43:28]
Because when I'm writing a list map,
[00:43:30]
I know exactly what I'm doing.
[00:43:32]
But when I'm writing HTML map,
[00:43:34]
it sounds weird.
[00:43:36]
And what is that lowercase msg?
[00:43:38]
Exactly.
[00:43:40]
But to be fair, there are some
[00:43:42]
challenges as well. Like this, how
[00:43:44]
do you convert the top level message?
[00:43:46]
The nested
[00:43:48]
command message needs to be
[00:43:50]
handled just
[00:43:52]
in the same way. And I think that's
[00:43:54]
also something else that
[00:43:56]
shows the importance of
[00:43:58]
having effects. That if you
[00:44:00]
need to have some shared effects
[00:44:02]
or effects that need to
[00:44:04]
introspect, it's so nice
[00:44:06]
when you have your own type because
[00:44:08]
the command message type is quite opaque.
[00:44:10]
You can't do anything with it
[00:44:12]
anymore. Like once you receive it,
[00:44:14]
the only thing you can do is to propagate
[00:44:16]
it. So having a way to
[00:44:18]
inspect that, I think both in tests
[00:44:20]
and in real code
[00:44:22]
is quite valuable. Because sometimes
[00:44:24]
you just don't want to do it straight away.
[00:44:26]
You want to wait a little bit, right?
[00:44:28]
Or you want to take a look, peek
[00:44:30]
inside the effect before
[00:44:32]
deciding to execute it.
[00:44:34]
That's actually, you know,
[00:44:36]
I often say wrap early,
[00:44:38]
unwrap late. And that actually lines
[00:44:40]
up with that because if you,
[00:44:42]
in a sense, you're sort of
[00:44:44]
creating this type that is
[00:44:46]
this lowest common denominator.
[00:44:48]
When you create a command, it's the
[00:44:50]
lowest common denominator. And you want
[00:44:52]
to sort of create this type that
[00:44:54]
you need for the lowest common
[00:44:56]
denominator to pass to the outside world, whether
[00:44:58]
it's a JSON type, a command
[00:45:00]
type. And you want to create
[00:45:02]
that low level lowest
[00:45:04]
common denominator type that the outside world
[00:45:06]
needs as late as possible.
[00:45:08]
Because like you said,
[00:45:10]
it limits your ability to
[00:45:12]
inspect it, interact with it,
[00:45:14]
modify it if you turn it into the
[00:45:16]
lowest common denominator right away. And it's
[00:45:18]
also less constrained because it just could
[00:45:20]
be many things instead of
[00:45:22]
having a clear type.
[00:45:24]
When you said that, I was thinking about JSON
[00:45:26]
values, right? Like, eventually
[00:45:28]
there would be a moment where we take our model
[00:45:30]
and we need to encode it into a
[00:45:32]
JSON value, which is this opaque
[00:45:34]
type. But imagine if that's
[00:45:36]
the first thing that we did, right?
[00:45:38]
We wouldn't even be able to change the model
[00:45:40]
after we encode it to a JSON, right?
[00:45:42]
When it gets to this
[00:45:44]
base level
[00:45:46]
of information, then you
[00:45:48]
can't do anything with it anymore.
[00:45:50]
You can decode it again.
[00:45:52]
Right.
[00:45:54]
It's a great pattern.
[00:45:56]
Every function. Every function
[00:45:58]
called, decode, encode.
[00:46:00]
You mentioned this sort of
[00:46:02]
quill web component in the blog
[00:46:04]
post and that's come up in other
[00:46:06]
conference talks about No Red Ink.
[00:46:08]
How widespread are web components
[00:46:10]
in the No Red Ink codebase?
[00:46:12]
Could you maybe give a short summary
[00:46:14]
of what you use web components for?
[00:46:16]
Yes, of course.
[00:46:18]
Basically, web components
[00:46:20]
are part of the HTML
[00:46:22]
specification.
[00:46:24]
There are ways
[00:46:26]
where you can create
[00:46:28]
basically new HTML tags.
[00:46:30]
You can create your
[00:46:32]
own elm dash editor.
[00:46:34]
You have complete
[00:46:36]
control when this node
[00:46:38]
is added to the DOM, how
[00:46:40]
it needs to react in case
[00:46:42]
events happen.
[00:46:44]
You basically have this little API where you
[00:46:46]
can create your own new tag.
[00:46:48]
I think in the
[00:46:50]
No Red Ink codebase,
[00:46:52]
we use it whenever we want to
[00:46:54]
interface ourselves with something
[00:46:56]
that it's awkward to do
[00:46:58]
in pure elm. I think, for example,
[00:47:00]
the case of quill is a good
[00:47:02]
one because we all know that
[00:47:04]
content editable
[00:47:06]
is a very difficult HTML
[00:47:08]
API to work with.
[00:47:10]
The behavior of the API
[00:47:12]
is weird. It has a lot of
[00:47:14]
edge cases. It doesn't behave
[00:47:16]
in a nice way. I think many people
[00:47:18]
think, okay, I'm going to create
[00:47:20]
this little wrapper around
[00:47:22]
content editable. Then, three
[00:47:24]
days later, they come back and say,
[00:47:26]
oh no, this is just too difficult.
[00:47:28]
I mean, there was the
[00:47:30]
Implementing Elm podcast, which had a whole
[00:47:32]
season about this topic.
[00:47:34]
Right, right.
[00:47:36]
That's what I'm saying.
[00:47:38]
It's just very
[00:47:40]
difficult to do it
[00:47:42]
in a good way.
[00:47:44]
Even for a normal desktop browser.
[00:47:46]
Then you start thinking about
[00:47:48]
mobile browsers, Safari
[00:47:50]
mobile, and you just
[00:47:52]
realize that it's just too much of a
[00:47:54]
task for a single human
[00:47:56]
being in its lifetime.
[00:48:00]
I think this is one of the cases where
[00:48:02]
using a web component
[00:48:04]
allows you to use
[00:48:06]
some JavaScript code that someone else
[00:48:08]
has written, which handles all of these
[00:48:10]
API edge cases.
[00:48:12]
But at the same time, it gives you
[00:48:14]
this little seam,
[00:48:16]
this little bridge, and you can
[00:48:18]
send data to the thing
[00:48:20]
from Elm, receive data
[00:48:22]
from the web component,
[00:48:24]
and be able to integrate
[00:48:26]
it seamlessly
[00:48:28]
in your Elm application.
[00:48:30]
I think a word of warning is that
[00:48:32]
since it's JavaScript code,
[00:48:34]
any error in the web component
[00:48:36]
will cause crashes.
[00:48:38]
I think that's the
[00:48:40]
word of warning.
[00:48:42]
Every moment you get a little bit outside
[00:48:44]
of the Elm world
[00:48:46]
and you just start executing
[00:48:48]
JavaScript code, you open
[00:48:50]
yourself to random crashes.
[00:48:52]
In fact, they do happen in some
[00:48:54]
I can see it in our bug tracker
[00:48:56]
every once in a while, the library
[00:48:58]
just hits some edge case and just crashes.
[00:49:00]
But I think it's quite
[00:49:02]
really nice. If you've never
[00:49:04]
done it, I definitely
[00:49:06]
recommend trying creating a simple
[00:49:08]
web component that does something.
[00:49:10]
And
[00:49:12]
another case where we use it quite a lot
[00:49:14]
is, for example, for read aloud.
[00:49:16]
We want kids to be
[00:49:18]
able to listen
[00:49:20]
to portions of the page being
[00:49:22]
read aloud.
[00:49:24]
And we haven't spent
[00:49:26]
the time to write a pure Elm
[00:49:28]
voice synthesizer.
[00:49:30]
And instead, we know that the browser
[00:49:32]
has some good support, so
[00:49:34]
we just include this web component
[00:49:36]
everywhere and we pass the data in
[00:49:38]
from Elm. The web component just
[00:49:40]
renders itself and
[00:49:42]
it knows that whenever
[00:49:44]
someone presses the button, it needs to
[00:49:46]
read out the contents
[00:49:48]
of the section of the page.
[00:49:50]
And we don't have to worry about that.
[00:49:52]
And it also means that
[00:49:54]
you don't need to set up
[00:49:56]
the port interaction.
[00:49:58]
Right. Yeah.
[00:50:00]
You can do it declaratively.
[00:50:02]
And this
[00:50:04]
also means that, for example, as we
[00:50:06]
were mentioning before, it just makes some
[00:50:08]
things hassle free.
[00:50:10]
You don't need to set up all the
[00:50:12]
glue logic, sense things
[00:50:14]
back and forth. You just create it.
[00:50:16]
You know that this is self contained. We will
[00:50:18]
do one thing and do it well. Hopefully
[00:50:20]
it won't crash.
[00:50:22]
But I think, yeah, it's
[00:50:24]
really nice way of making
[00:50:26]
hard things simple.
[00:50:28]
And even in Elm,
[00:50:30]
there's some things which are very hard to do.
[00:50:32]
And if you figure out the right
[00:50:34]
way to solve this problem, why not?
[00:50:36]
I feel definitely every
[00:50:38]
time I write, I work in that
[00:50:40]
area of the code, I'm three
[00:50:42]
times more careful
[00:50:44]
than what I'm writing in Elm code.
[00:50:46]
It's like every time you're
[00:50:48]
sort of, you know that
[00:50:50]
if you make a mistake here, it's going to be
[00:50:52]
bad. And instead when I'm
[00:50:54]
making the changes in the Elm code
[00:50:56]
base, I'm like, yeah, that's fine.
[00:50:58]
You know, like, eventually some
[00:51:00]
something will catch the problem. But whenever
[00:51:02]
I'm writing a web
[00:51:04]
component, I know that I could be
[00:51:06]
causing the whole page to crash if I'm not
[00:51:08]
careful here. You use it in
[00:51:10]
some key places, but you try to use it sparingly
[00:51:12]
for a handful of
[00:51:14]
of these helpers.
[00:51:16]
And I think especially in these cases,
[00:51:18]
let's say an editor, it's
[00:51:20]
really awkward if you want to implement
[00:51:22]
using a part, right? Because there's so
[00:51:24]
many events that can go towards
[00:51:26]
the editor. Now you can write
[00:51:28]
text, you can select text, you can change
[00:51:30]
the style of the text, you want to add
[00:51:32]
an image. There's like so much stuff
[00:51:34]
that you need to thread
[00:51:36]
through, right? If you wanted to write a part.
[00:51:38]
But instead, if
[00:51:40]
all you're doing is to send some data,
[00:51:42]
receive some data back that you need
[00:51:44]
to serialize, then
[00:51:46]
that's not too bad.
[00:51:48]
Right. And we often say
[00:51:50]
in the Elm community how
[00:51:52]
don't write
[00:51:54]
components. Components are
[00:51:56]
an object oriented sort of
[00:51:58]
paradigm and
[00:52:00]
write helpers and
[00:52:02]
pass state around. But
[00:52:04]
you can write web components.
[00:52:06]
And that's what web components are there for.
[00:52:08]
They can encapsulate state. They are
[00:52:10]
components and you can use them
[00:52:12]
declaratively and not worry about the state.
[00:52:14]
That's someone else's problem.
[00:52:16]
Just like using
[00:52:18]
you know, if you use
[00:52:20]
like a detail element from
[00:52:22]
the browser API, then
[00:52:24]
you've got this expanded
[00:52:26]
collapse state. You don't need to
[00:52:28]
own that somewhere. The browser owns it
[00:52:30]
and that's good. It's declarative state.
[00:52:32]
It's actually super fun, right?
[00:52:34]
Because I think we think of web
[00:52:36]
components as the part where the JavaScript
[00:52:38]
exists. But actually there's
[00:52:40]
this library which allows you to add
[00:52:42]
Elm bits
[00:52:44]
to a React application and it does
[00:52:46]
so through web components.
[00:52:48]
Inside the Elm lives
[00:52:50]
inside the web component and
[00:52:52]
it lives within the larger
[00:52:54]
context of a React application.
[00:52:56]
So, you know.
[00:52:58]
That's awesome.
[00:53:00]
I mean,
[00:53:02]
I think that there's room for some nice
[00:53:04]
tooling around using web components
[00:53:06]
in Elm in general.
[00:53:08]
But it would be really interesting
[00:53:10]
to have some tooling for
[00:53:12]
creating a little Elm
[00:53:14]
component and embedding that
[00:53:16]
in your app as a web component. I wonder,
[00:53:18]
I'm not sure if that would be a good idea, bad idea, but
[00:53:20]
seems like...
[00:53:22]
I've done it at least a few times.
[00:53:24]
I once had to rewrite an Elm application
[00:53:26]
to React entirely
[00:53:28]
and I did it bit by bit. So,
[00:53:30]
at the start I was injecting
[00:53:32]
React through web components
[00:53:34]
in an Elm application and
[00:53:36]
then more and more. And then at some point I had
[00:53:38]
to reverse everything. So now it was
[00:53:40]
an Elm app that was
[00:53:42]
using React
[00:53:44]
web components inside it and it was itself
[00:53:46]
wrapped in a
[00:53:48]
giant React app.
[00:53:50]
That was kind of fun.
[00:53:52]
No, it actually was fun because
[00:53:54]
I kind of like that. But
[00:53:56]
the quality did drop because
[00:53:58]
it was an Elm. But I think that's a
[00:54:00]
testament to the power of
[00:54:02]
these HTML API. I think a lot
[00:54:04]
of times, you know, we're quite critical,
[00:54:06]
you know, like drag and drop
[00:54:08]
or content editable.
[00:54:10]
But I think there are some
[00:54:12]
HTML APIs there
[00:54:14]
which are really nice, really powerful.
[00:54:16]
They're at the right level of abstraction
[00:54:18]
and they just work
[00:54:20]
really well. So we should probably link to
[00:54:22]
the Elm community
[00:54:24]
slash JS integration examples
[00:54:26]
repo, which has some examples
[00:54:28]
with working with web components and
[00:54:30]
other JavaScript
[00:54:32]
interop techniques.
[00:54:34]
I can also link that React
[00:54:36]
components that I was mentioning.
[00:54:38]
I'm sure it's like
[00:54:40]
I can find it. I will search it and
[00:54:42]
send it to you. That'd be great.
[00:54:44]
So are there any other
[00:54:46]
things that
[00:54:48]
you think would be interesting for
[00:54:50]
listeners to hear about, Ju, that
[00:54:52]
you've learned from your experience working
[00:54:54]
on this large Elm code base?
[00:54:56]
The thing which I left, you know, as
[00:54:58]
the last cherry on top.
[00:55:00]
I think it's Elm Review. I think
[00:55:02]
we just love it.
[00:55:04]
In large code
[00:55:06]
base, a lot of code
[00:55:08]
just goes unused.
[00:55:10]
And I'm just talking
[00:55:12]
about a single Elm Review
[00:55:14]
plugin package.
[00:55:16]
But I think that one
[00:55:18]
for me is the nicest
[00:55:20]
one. Just to show you how
[00:55:22]
much code gets
[00:55:24]
written and just abandoned.
[00:55:26]
And then for ages it's
[00:55:28]
lived there in that file and nobody
[00:55:30]
cares about it. And then suddenly you
[00:55:32]
have a tool that says, well, all the code
[00:55:34]
that you don't use, how about we just chuck it?
[00:55:36]
And then you rerun
[00:55:38]
the build, nothing fails.
[00:55:40]
And I think it's just like so
[00:55:42]
good when you have a large code base
[00:55:44]
where a lot of the changes that
[00:55:46]
you make are quite
[00:55:48]
localized.
[00:55:50]
And every once in a while, someone
[00:55:52]
leaves the company and all that context is
[00:55:54]
gone. And we make a little
[00:55:56]
change. And it's like
[00:55:58]
all is good when the compiler
[00:56:00]
compiles. But I think there's
[00:56:02]
still some loss of
[00:56:04]
information or there's some extra
[00:56:06]
information which is not needed
[00:56:08]
anymore. But when you're trying to regain
[00:56:10]
the context by reading the source,
[00:56:12]
you have to still
[00:56:14]
understand it. And it's so nice to know
[00:56:16]
that, no, everything that I'm reading
[00:56:18]
in this file matters.
[00:56:20]
There's no more guesswork to be done.
[00:56:22]
And I think that's
[00:56:24]
what really makes me excited about
[00:56:26]
Elm Review. I think maybe
[00:56:28]
it's interesting to
[00:56:30]
know that you don't have
[00:56:32]
to apply it
[00:56:34]
on your whole code base.
[00:56:36]
So I think that originally
[00:56:38]
was my problem because I ran it initially
[00:56:40]
maybe a year ago
[00:56:42]
on the repository
[00:56:44]
and it yielded
[00:56:46]
30,000
[00:56:48]
errors. Some of them
[00:56:50]
could be auto fixed.
[00:56:52]
Some of them could not be auto fixed.
[00:56:54]
So it's a difficult thing.
[00:56:56]
If you had to
[00:56:58]
say, okay, I'm going to spend the next
[00:57:00]
three weeks fixing Elm Review
[00:57:02]
problems.
[00:57:04]
So I feel like it's really important
[00:57:06]
when you're in that situation to come up with
[00:57:08]
a plan. So I think this is
[00:57:10]
what I mentioned before.
[00:57:12]
I wrote this very simple Ruby script
[00:57:14]
that just selected one
[00:57:16]
little slice of our application
[00:57:18]
and just applied Elm Review on that.
[00:57:20]
The first thing I did was to
[00:57:22]
add that to our suite,
[00:57:24]
to our test suite
[00:57:26]
so that the moment
[00:57:28]
that little slice of the code
[00:57:30]
is covered, it's going to remain covered.
[00:57:32]
And someone is going to get annoyed
[00:57:34]
because their build is going to fail
[00:57:36]
because they deleted
[00:57:38]
some code and didn't clean it up.
[00:57:40]
But I think having this
[00:57:42]
guarantee just helps you that
[00:57:44]
you don't feel you're
[00:57:46]
alone fighting
[00:57:48]
this big war.
[00:57:50]
You have these little
[00:57:52]
things that you clean up and they remain
[00:57:54]
clean. And I think over the course
[00:57:56]
of maybe, as I said, like six
[00:57:58]
months, every once in a while I would
[00:58:00]
work a little bit on them. But
[00:58:02]
it's like this, as I said, it's like another
[00:58:04]
case of this compound interest.
[00:58:06]
You make this effort, but it's not
[00:58:08]
wasted. And I can think a lot
[00:58:10]
of work which I've done in the past
[00:58:12]
where I try to clean up this pit of
[00:58:14]
code, and then maybe I can't
[00:58:16]
really communicate well the intent
[00:58:18]
or
[00:58:20]
I change
[00:58:22]
my mind throughout and I forget
[00:58:24]
to go and change the other
[00:58:26]
code which I've written before.
[00:58:28]
And having a tool that can do
[00:58:30]
that, that you can express what you believe
[00:58:32]
is the right thing to do,
[00:58:34]
and having it run at every round of the
[00:58:36]
CI is just
[00:58:38]
invaluable. Sorry,
[00:58:40]
I ranted a lot, but I'm really excited
[00:58:42]
about this. You can
[00:58:44]
rant for hours.
[00:58:46]
I'm happy to hear it.
[00:58:48]
I think
[00:58:50]
that you would have an easier time
[00:58:52]
today doing this because some
[00:58:54]
rules have more fixes than a year
[00:58:56]
ago or two years ago. Two years
[00:58:58]
ago. Yeah, it's barely
[00:59:00]
two years old.
[00:59:02]
Also, there's the
[00:59:04]
suppression system now.
[00:59:06]
I've read in
[00:59:08]
the change log, it looks cool.
[00:59:10]
Yeah, I think it would make it a lot
[00:59:12]
easier.
[00:59:14]
You definitely want to
[00:59:16]
make sure that everything goes out
[00:59:18]
at some point. But I guess
[00:59:20]
this is also related to what we were saying
[00:59:22]
before, that just because a tool exists,
[00:59:24]
I feel like I really recommend
[00:59:26]
people to try to play around with things
[00:59:28]
and think, this tool is
[00:59:30]
doing this, but it's not exactly what I like.
[00:59:32]
What is a very
[00:59:34]
easy experiment that can run
[00:59:36]
and I can just
[00:59:38]
lie to the tool. I can tell the tool,
[00:59:40]
this is the whole project, just run on this.
[00:59:42]
Having
[00:59:44]
this very quick and simple example
[00:59:46]
will tell you, is this really
[00:59:48]
what I want? Is this what I need? Before
[00:59:50]
you commit to making the big change
[00:59:52]
or adopting
[00:59:54]
the technology. I think just like this
[00:59:56]
idea of being able to create
[00:59:58]
short and small experiment
[01:00:00]
to validate your assumptions
[01:00:02]
is very important.
[01:00:04]
Are there any other
[01:00:06]
Elm review rules you can think of that
[01:00:08]
you've been making use of that
[01:00:10]
have been particularly helpful? Or has
[01:00:12]
it mostly been the unused stuff
[01:00:14]
that's been the most valuable?
[01:00:16]
The unused stuff for me
[01:00:18]
is the most valuable, like
[01:00:20]
unused constructors, unused functions,
[01:00:22]
unused imports.
[01:00:24]
I've seen some new rules
[01:00:26]
that are coming out, which I think
[01:00:28]
they look really good.
[01:00:30]
I think that's also something that I've
[01:00:32]
been planning to experiment with.
[01:00:34]
Even though I want to be
[01:00:36]
careful also with the runtime
[01:00:38]
of running Elm review
[01:00:40]
on the whole code base.
[01:00:42]
Right now, I think we've gotten to a point
[01:00:44]
where I just run Elm review.
[01:00:46]
Maybe it takes, in our
[01:00:48]
code base, probably takes a couple of minutes
[01:00:50]
to run. I'm sorry.
[01:00:52]
So
[01:00:54]
that's also why I haven't been
[01:00:56]
adding all the rules, because I still
[01:00:58]
want people to be able to have that sort
[01:01:00]
of medium
[01:01:02]
feedback loop when you can still
[01:01:04]
run something on your computer.
[01:01:06]
To give you a sense of perspective,
[01:01:08]
our Ruby suite
[01:01:10]
takes 20 minutes to run
[01:01:12]
on CI, where we break
[01:01:14]
it into 20 chunks.
[01:01:16]
So if you had
[01:01:18]
to run this locally on your computer
[01:01:20]
with just a single browser, it would take
[01:01:22]
400 minutes to run
[01:01:24]
this thing. So I think that's the
[01:01:26]
sort of meter of judgement.
[01:01:28]
I want something that, in the course
[01:01:30]
of a tea break,
[01:01:32]
it can give you an answer,
[01:01:34]
an interesting answer.
[01:01:36]
And I think up to when it's
[01:01:38]
at that point, a feedback loop is
[01:01:40]
still okay. But
[01:01:42]
anything slower than that, I feel, is
[01:01:44]
just too much friction
[01:01:46]
to ask someone to use this
[01:01:48]
as part of your daily work, though.
[01:01:50]
Yeah, I definitely agree.
[01:01:52]
Running Elm review in
[01:01:54]
my small packages
[01:01:56]
is just so fun.
[01:01:58]
But running it on my
[01:02:00]
work project feels painful
[01:02:02]
because we have a lot of rules and we also have a
[01:02:04]
pretty large code base.
[01:02:06]
And yeah, it takes maybe a minute
[01:02:08]
or maybe slightly less than a minute.
[01:02:10]
And it already feels so painful.
[01:02:12]
Also,
[01:02:14]
partially because I'm the author and I know
[01:02:16]
I need
[01:02:18]
to find a way to make it faster.
[01:02:20]
And I have some ideas, but
[01:02:22]
yeah, everything takes time.
[01:02:24]
But I remember that when I
[01:02:26]
presented Elm review
[01:02:28]
at the Paris
[01:02:30]
meetup the first time,
[01:02:32]
one of the first questions I got was
[01:02:34]
how fast does it run?
[01:02:36]
Like, what can you do with it or how
[01:02:38]
how sound are
[01:02:40]
the reported errors? It's like, how
[01:02:42]
fast is it? And I was like, I don't
[01:02:44]
know. When's the Rust version
[01:02:46]
coming out?
[01:02:50]
Is there like a tree seed
[01:02:52]
implementation of Rust? I'm sure
[01:02:54]
there is. There must be. I think
[01:02:56]
there is, but it might
[01:02:58]
be the tree seeder for Rust
[01:03:00]
that I'm confusing it with.
[01:03:02]
But yeah, I think if I think
[01:03:04]
of the recent changes we've
[01:03:06]
introduced, I'm
[01:03:08]
really euphoric
[01:03:10]
about adding
[01:03:12]
Elm review.
[01:03:14]
And it's not just me. I think a lot of
[01:03:16]
people are saying, oh, this is
[01:03:18]
really great. Just having
[01:03:20]
I think it's just this sort of
[01:03:22]
other mind that takes a look
[01:03:24]
at the whole code base and gives you this
[01:03:26]
this other representation of it
[01:03:28]
saying, okay, these other constraints
[01:03:30]
which are not enforced by the compiler
[01:03:32]
that are enforced by this other
[01:03:34]
sort of compiler. If you squint
[01:03:36]
hard enough, it's just another compiler
[01:03:38]
really. And it's
[01:03:40]
doing some other work.
[01:03:42]
You can enforce some
[01:03:44]
other rules.
[01:03:46]
I think before we were mentioning deprecation,
[01:03:48]
right? So I think this is
[01:03:50]
something that Elm review can
[01:03:52]
do really well. You have this module
[01:03:54]
and it's calling this method and you
[01:03:56]
would like to deprecate it in a
[01:03:58]
soft way maybe at first.
[01:04:00]
You can very easily write
[01:04:02]
an Elm review rule.
[01:04:04]
I think the beauty about this sort of work
[01:04:06]
is that it's quite infectious.
[01:04:08]
So from taking inspiration
[01:04:10]
from Elm review,
[01:04:12]
we also written our own
[01:04:14]
rules for RuboCop, which is
[01:04:16]
the sort of Ruby
[01:04:18]
linking tool that we use.
[01:04:20]
And I think if I had never
[01:04:22]
seen how to write an Elm review rule,
[01:04:24]
I would have never bothered
[01:04:26]
writing a RuboCop rule.
[01:04:28]
But once I've done one,
[01:04:30]
I'm like, yeah, it's basically the same thing.
[01:04:32]
You walk through this thing and this
[01:04:34]
is how the
[01:04:36]
function call is expressed
[01:04:38]
in the Ruby AST.
[01:04:40]
And once you've done
[01:04:42]
one, looking at the other is just
[01:04:44]
a game
[01:04:46]
of find the differences.
[01:04:48]
Yeah. I'll be curious
[01:04:50]
in a year to check
[01:04:52]
back and hear because
[01:04:54]
I mean, it sounds like the sort of automation
[01:04:56]
you do running Python
[01:04:58]
scripts and Ruby scripts and
[01:05:00]
TreeSitter scripts and that
[01:05:02]
sort of thing to analyze the
[01:05:04]
code base and automate things.
[01:05:06]
I can imagine there will be more
[01:05:08]
custom no red ink Elm review
[01:05:10]
rules coming. So I'll
[01:05:12]
be curious to check back in on that.
[01:05:14]
And by the time Jaren will
[01:05:16]
have written completely in Rust
[01:05:18]
and will run like in one second,
[01:05:20]
then all
[01:05:22]
our problems will be gone.
[01:05:24]
No pressure.
[01:05:26]
I think
[01:05:28]
that's the main takeaway of this episode.
[01:05:30]
Jaren's rewriting Elm review in Rust.
[01:05:32]
Can't wait.
[01:05:34]
All right.
[01:05:36]
To be fair, I feel that
[01:05:38]
the Elm test runner, which is written
[01:05:40]
in Rust, is not that
[01:05:42]
significantly, significantly
[01:05:44]
faster than the one written in Node.
[01:05:46]
So maybe there are some problems
[01:05:48]
which can't be just magically
[01:05:50]
fixed by Rust.
[01:05:52]
Yeah.
[01:05:54]
Yeah, the issue is that
[01:05:56]
it would take a lot of time to
[01:05:58]
rewrite it in Rust just
[01:06:00]
to make sure that there are
[01:06:02]
improvements.
[01:06:04]
More likely Jaren is just going to
[01:06:06]
make Elm 50
[01:06:08]
times faster with his
[01:06:10]
Elm optimized level two improvements
[01:06:12]
he's been working on and then
[01:06:14]
problem solved. Yeah, exactly.
[01:06:18]
All right. Well,
[01:06:20]
Ju, thanks so much for coming
[01:06:22]
on.
[01:06:24]
I believe the last I heard,
[01:06:26]
No Red Ink has open
[01:06:28]
positions for Elm
[01:06:30]
developers and for Haskell developers.
[01:06:32]
Is that correct? Yeah, that's
[01:06:34]
absolutely correct. So feel free
[01:06:36]
to check us out. Yeah, I've been
[01:06:38]
working here for some time. I'm
[01:06:40]
still here. So I think that's
[01:06:42]
okay.
[01:06:44]
That's all we can ask for as developers, like for
[01:06:46]
a place where you just done, you know,
[01:06:48]
dread it. And I think
[01:06:50]
that's pretty much good.
[01:06:52]
You can work on Elm and it feels
[01:06:54]
just like working on a small Elm application.
[01:06:56]
Yeah.
[01:06:58]
And where can
[01:07:00]
people go to follow you and find
[01:07:02]
out more about you? You can just go to my
[01:07:04]
website. It's called
[01:07:06]
ju.liu.is
[01:07:10]
and that's all. There are all my
[01:07:12]
little useless blog posts
[01:07:14]
about useless projects.
[01:07:16]
And maybe there's a couple of useless
[01:07:18]
articles, useful articles, but
[01:07:20]
that's the ratio of, you know,
[01:07:22]
signal to noise.
[01:07:24]
Delightful, delightful articles.
[01:07:26]
Actually, Jeroen and I used your
[01:07:28]
Elm performance articles quite a bit for
[01:07:30]
our performance episodes. So lots of good stuff.
[01:07:32]
Check it out. Well, thanks again for coming
[01:07:34]
on. Ju, it was a pleasure having you.
[01:07:36]
Thank you so much. And Jeroen,
[01:07:38]
until next time.
[01:07:40]
Until next time.