elm radio
Tune in to the tools and techniques in the Elm ecosystem.
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.
Published
February 14, 2022
Episode
#50
Ju Liu (
twitter
) (
github
)
Elm at NoRedInk
Ju's blog
noredink-ui
is NoRedInk's internal UI kit (
live demo page
)
avh4/elm-program-test
elm-sortable-table API
Implementing Elm Podcast season 1
cultureamp/react-elm-components
(React library for embedding Elm as a Web Component)
elm-community/js-integration-examples
NoRedInk is hiring Elm and Haskell devs
Ju Liu's blog
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.