elm-app-url with Simon Lydell

Simon Lydell joins us to talk about a new approach to URL parsing in Elm that is both simple and powerful.
February 27, 2023


Hello, Jeroen.
Hello, Dillon.
Well, I thought about talking about the subject just the two of us, but I thought it would
be a lot better if we brought in a domain expert.
What do you think?
I think that sounds great.
And that domain expert is Simon Lydell.
Simon, thank you so much for coming back on the podcast.
Glad to be here and great to be back.
Yeah, you are our domain expert.
So are you trying to force a pun?
No, you.
Simon, what are we talking about today?
Today we're talking about elmappurl, which is a new package that I made, which is related
to a domain, but it's also exactly what it isn't.
So it was a bad pun.
So what exactly does it do?
elmappurl is an alternative slash complement to the standard elm slash URL package, and
it attempts to be simpler and easier to use.
So you talked about, you've got a really nice short intro video that I definitely recommend
people take a look at.
We talked about this blog post that sort of went into all of the types behind the elm
URL parsing, and you completely bypassed that with your design here.
So the way I'd like to see it is there are like three main things that they're dissatisfied
with the official elm slash URL package.
And they are more or less a design flaw and escape incident and the Jurassic Park anti-pattern.
I'm intrigued.
Me too.
And we probably need to keep that one for last since you mentioned it last.
So let's get started with the rest.
I actually think it makes sense to start with a Jurassic Park.
Oh, great.
It's the most interesting part and it ties back to what Dillon said with the intro in
the readme.
Is it that our scientists stopped to ask if they could, but not whether they should?
Yeah, exactly.
That's the whole thing.
So in my opinion, URL.parser in the standard elm URL package, in hindsight, maybe just
should have been a blog post and not a package.
And what I mean by that is like, I read a tweet, like, I don't know, a year ago, where
someone said, like, did you know that all URLs are also valid JavaScript code?
And then they explained like, if you start with the HTTP colon, that's a labeled statement
in JavaScript.
Oh, no.
And then you have slash slash, and that's a comment.
And the rest of it is just the rest of the comments.
So like, that's a fun thing.
That's something I could see myself waking up thinking like, I never thought about that
I'm going to try if that works.
And maybe it does.
And I learned something.
And I like to imagine, this is of course not true.
This is just my imagination.
What if Evan woke up one day and was like, could I like take a URL and have it be valid
And if you think about it, if you have like a URL, let's say HTTP colon slash slash
slash A slash B, and you only take like the last part slash A slash B, you get rid of
the first slash, then you only have A slash B. And like, that looks like division.
So you could probably make that work as code, define A and define B, set them to floats.
And when you do the division, you get another number as the result, right?
And then you could like take it one step further in Python, you could do operator overloading.
So if you make a class in Python and define a certain method, then you can like decide
what it means to do division with two such classes, for example.
And then you could like decide that, okay, division means that I'm going to return not
a number, but maybe a function.
And that function could do something interesting, like parse a URL that looks like A slash B
and tell you like, it was A slash B or it wasn't.
But then in Elm, we don't have operator overloading.
But before Elm 0.19, we had custom operators.
So you can define your own, which is exactly what Elm slash URL package does.
And slash was occupied for division, it like added less than and greater than around the
slash sign.
And now you have an operator that kind of looks like a slash.
And then you could like create a parser type and define some small functions that you could
use with this slash.
And then you can like kind of take a URL, paste it in your Elm code, tweak it a little
bit and then you have like a working parser for that URL.
And that is a really cool idea in my opinion.
But now we have talked about like, let's think about if we could do it.
But should we do it?
And in my opinion, like you could have written a blog post, how you came up with this, how
you implemented it, what you learned, and then also pros and cons of the approach.
And the pros is maybe that perhaps you could take a URL and turn it into code quite easily.
But the cons in my opinion is that there is quite a lot of type trickery involved to make
this work.
That is fairly non obvious to work with, at least as a beginner, I think.
And also for me.
Yeah, so you're mostly talking about parsing a URL, right?
So you have a string and you give it a specification, three parser type, and then you run it and
the types that are involved in that are very complex.
Yeah, there is a parser type in the package.
And it takes a, or it makes sense if it takes a type variable, because if you're parsing
something, you want to decide like what type should it parse to, what type have I chosen
to represent all my routes in an app with.
But if you look at the actual parser type, it doesn't take just one type variable, it
takes two of them.
And every time I try to write a type annotation for a URL parser, I can start out quite easily.
I type like URL dot parser space, and then I'm stuck.
And I can't figure it out.
So I just erase all of that, and then I use my editor action instead, infer type, and
it puts it in for me.
And the first type variable is a function that like takes my type and returns an A.
And the second one is also an A. And I have no clue what the function is doing there and
what the A is.
So having been through this process yourself, can you, in simple language, explain what
that extra type variable actually means?
Because I still find it extremely confusing.
I read the blog post about it, but I just don't remember.
It's just gone.
We will then link to the blog post and we can all get out of having to put that into
simple words.
Is it kind of like monads?
Whenever you read about them and you understand, then you can't explain it again.
In this case, you read it, you don't understand it, and you still can't explain it.
And then you design a whole package so that you don't have to deal with that anymore.
And then you're invited on a podcast and asked to explain it.
And then your name is Simon.
Whoa, that's very specific.
Classic scenario.
So you designed a Jurassic Park version that's the well there it is version of the Jurassic
Park takeaway.
And so what were the...
You talked about the pros and cons.
So the cons are you have this challenging type to deal with.
The pro is it kind of looks like a URL, but it's not necessarily...
See that's an interesting question is, does that make it easier to understand what it's
doing because it looks like a URL?
And I would say, if you step back and look at Elm's history a little bit, in the earlier
days, there was, I think there were more roots in kind of Haskell design and a little bit
like in the ecosystem of Ruby design.
Like Richard Feldman's Elm CSS early on was trying to use a syntax where you design these
style sheets that looked like CSS syntax.
So you could have a custom operator for hashes and a custom operator for dots.
And there were like a few things that didn't quite work out neatly.
So you had to use some special trick for that.
And then eventually he just got rid of all of them and he said, this actually, like,
yes, it makes it kind of look like CSS syntax, which is kind of cool.
And in the Ruby community, people really liked making DSLs that looked like the thing they're
supposed to be.
But then he moved to a more straightforward design that focused on making it really easy
to understand how the pieces fit together and making it more maintainable.
And I think like a lot of the Elm design instincts have moved in that direction too.
And to me, your design for Elm app URL feels like that as well.
So should we explain how parsing was done before?
Like how did the code look and how it's done in your solution?
So we need some kind of example URL to talk about.
And maybe we can do like slash product slash, and then like a product ID or a product slug.
We can go with a product slug.
So we have a hard coded part, which is always product and then slash and then a dynamic
And then the official package, you would, you would use this function called just S.
I think it's for segment.
Oh, I thought it was for string, segment makes sense.
I think so.
At least I think the point is that it's supposed to be short.
So the code looks more like a URL and less like code.
And the segment function, it takes a string and there we would give it a product as a
And that's how you say that I want a hard coded piece, which is product.
And then you use this custom slash looking operator to join that with the next piece
of the URL, which will be the dynamic part.
And then there's a function called string, which means that this segment can be any string.
And then finally, you usually pipe all of that to URL or parser dot map to turn that
into like a custom type variant.
I guess the most common thing.
But what I tried to do was to like, I wanted elm app URL to be more of an anti package.
I want to be a small and as little of a package as possible, just to be a stark contrast to
the official package.
I was thinking like, do we need a parser at all?
Can we can we just do it some simple way?
And what is the like the simplest way I could think of?
Well, we could take the path of a URL and just split by slash and then do pattern matching
on it with a case of expression.
So try that without a package.
I just like made a little example and wanted to see what I can do with it.
And the first thing I noticed was that it's kind of annoying to split by slash yourself,
because the path always starts with a slash, which means that you get a list with an empty
And then the first thing you're actually interested in, which would be the string product in this
case, and then all of the rest of the things.
And also, I noticed that the parsers in the official package, they also ignore a trailing
slash for you, which is kind of common that sometimes you end up with a trailing slash
and maybe your application uses it, maybe it doesn't.
And then you have to handle that as well in the pattern match, like add an extra case
for an empty string at the end.
So that was kind of annoying.
But then I thought, like, the official package has a URL type, which is just a record with
all the pieces of a URL and one of them is called path.
And that is like the slash product slash product slug piece.
I was thinking like, why is that just a string?
Couldn't you have parsed that for me into a list of segments already?
That would have been so much easier.
So I thought like, that would be the first change I would make to this package.
So then I like create a little helper function in my example that did that.
And now my parsing function got really nice because it's just all I'm doing in the function
is case URL dot path of, and then I match different string, different list patterns.
So to take our example, the pattern we're looking for is a list with just two elements.
The first one is the string product.
The second one is any string.
So you can just type the name of a variable there.
And in inside the pattern, all you need to do is return just whatever you want.
And that's it.
That's all there is to it.
That is much simpler.
So you need to have a default case for any other routes that you're trying to parse.
And I'm guessing that would return a 404 custom variant, usually?
Yeah, I've seen different ways of doing it.
So that's one way of doing it.
If you have a custom type with all your routes, you can just one more alternative, which is
not found or 404.
You can also choose to return a maybe in this function.
So either just some route that exists or nothing, which means that nothing matched and you can
handle that one level above and turn it into a 404 page.
Yeah, so this package is really a pattern as much as anything.
And the code is just to support that pattern.
And so in a nutshell, that pattern is like you've got a nice example showing your product
example here.
It's just a function that takes an app URL, and it returns a maybe route.
Or if you wanted to have a 404 route, be a variant, it could just be a route.
But that's all it is.
Maybe we should just explain what an app URL is.
You said the function took the app URL.
So app URL is the central type of my package.
The difference to the URL type in the official package is that an app URL is a subset of
a whole URL.
It is only the path, the query parameters and the fragment.
So it is not the scheme like HTTP, and it is not the domain, not the port.
And the reason for that is that that is the part that you are actually interested in when
writing an Elm application.
Your app is going to be hosted on some domain, but you don't really care about that in your
Elm code.
It doesn't matter.
So it's kind of annoying to have to...
Like if you ever want to create an app URL because a function needs one, you don't want
to specify a fake scheme, a fake domain and so on, just to be able to satisfy the type.
And I read through specifications for URLs, and there is no name just for path plus query
plus fragment.
So I had to invent something.
And eventually I came up with app URL.
It's kind of short, and it also shows that it's like designed to be used for an Elm app.
Yeah, that's my thinking.
And this type or this package is only designed for parsing and stringifying routes in your
application, right?
It's not designed to represent any URL like the Elm URL package is supposed to.
So the way you use this, walk us through how you would wire this in compared to how you
would wire in an Elm URL parser.
So in an Elm application where you use browser.application, there are a couple of places where you get
a URL in the init function, and also in URL changed, like the URL change message that
you get.
And what you do there is instead of giving that URL to a URL parser, you run a function
called app URL dot from URL, which turns a full URL into only the app URL parts.
And then you give that to your function that does the pattern match on the path.
And then you have basically replaced the parser based stuff you had from before.
So the app URL type, so it gives you these values that are easier to deal with in a pattern
So you have path is list of string, query parameters is a dict of string to list of
string, and then the fragment is a maybe string.
I'm guessing the fragment is probably not used that often in this context, but query
parameters are used commonly.
So how do you use the query parameters to in Elm app URL to parse into part of your
That ties back to one of those three issues that I had with the official package.
And this is the design flaw one, in my opinion.
So the official URL type, it has a field called query, I think, and that is just a string,
which means that the query parameters, the stuff that is from the question mark to the
end or to the fragment, if you have one, it's just a string.
I've tried to think of like, why is that?
There is a link in the Elm URL package to an RFC, you know, one of those like monospaced
formatted specification lucky thingies, which is like a specification for a URL.
And in that one, for some reason, they just say that the query is the part from the question
mark to the fragment.
And usually the query is the part from the question mark.
Yeah, kind of.
But for some reason, they just say like, yeah, and people typically put key value pairs in
So I'm wondering if like, Evan decided that, okay, I'm going to follow this RFC.
And if someone complains, like, why is the query string?
Well, it's in the RFC.
I followed it.
But in practice, like everyone uses the query string the same way.
It's key value pairs separated by ampersands.
With equals separating the keys and values.
So that is not in the spec.
As far as I can understand, specifications for URLs are pretty weird.
I read a tweet from Daniel Stenberg, the creator of Curl.
He tweeted something, someone responded and they responded with a question like, something,
something URL specification.
And Daniel's answer was just like, which specification question?
Yeah, that's a bit too highly.
These days, there is one from the WG committee.
They're the ones making the HTML specification these days.
They have like a URL specification that is a bit focused to JavaScript, because JavaScript
has a nice URL class these days with the parsed query parameters in it and stuff like that.
Isn't there like a URL params constructor or something like that?
Yeah, I think it's URL search params or something.
URL search params, right.
So I had the luxury of being able to piggyback from that.
Like whenever I thought like, hmm, how should this be handled in query parameters?
I could look that up and see, like, how did they do it?
Does it make sense?
Should I copy that?
And the answer was always yes.
So I decided that in an app URL query, it's not just a string, it's a dictionary.
So the whole thing is parsed for you.
So the one piece of information that might have meaning in a somewhat normal use of URL
query params would be the order, which you have the order, if you use the same query
param name multiple times, you have the order that those come in, but you don't have the
order of the relative keys.
But I mean, usually people just use them as key value pairs.
So I guess that would be kind of non standard.
Maybe we should clarify, so I chose a dict to store the parameters.
And when you use a dict, you get the side effect that all of the things in the dict
are always sorted by key alphabetically.
So if you really cared about the order of your params, then that's not really possible
with my package.
But I don't think that's a real use case.
Yeah, the only use case that I can see where this could be a problem is if you're trying
to compare the URL you had at the beginning with the stringified version of your app URL,
then if the order of keys are different, then you're gonna say, Oh, well, these are different.
Let's refresh the page or something.
I don't know.
And in that case, you have a problem.
But other than that, yeah.
But if you wanted to do something like having only ampersand, having five ampersands in
a row in a query parameter, that's a valid URL.
According to the URL spec, but not meaningful for your package.
I tried to like think of every edge case.
It's like if you have a lot of ampersands in a row, what does that mean?
And there are two possible interpretations.
One is that you just ignore all of the extra ampersands, like they like don't contribute
to anything.
You could also see it as they contain a key that is the empty string, and the value is
also the empty string.
So you have like an empty parameter between each ampersand.
But that's not really useful.
So a list of dict key is empty string and value is a list of five empty strings.
Love that.
So how many ampersands do you need to have so that ampersand becomes a key?
I do support like ampersand equals ampersand, which is like key is empty string, value is
empty string.
Because that's what the JavaScript version did and that's what WG spec said.
That seems very wise to use like these sort of JavaScript standards because there are
just so many people using this that it supported their main use cases.
So it seems like a smart thing to piggyback on.
I mean, as far as like the elm URL type being extremely unopinionated and as you say, just
saying there is no standard, so here's a string.
It seems reasonable because if it didn't do that, then you get sort of backed into a corner
as a user where those use cases are impossible.
Whereas if your user land package, elm app URL has a strong and very reasonable opinion
about that, then people don't need to use it if it doesn't support their use case.
So I think it's a nice state of things.
And it's also kind of nice that elm doesn't have any built in assumptions about you using
the official core elm URL package, except for the URL type, which is just the sort of
raw URL value.
You don't need to use a URL parser if you don't want to, which is quite nice.
That is pretty nice.
So if you wanted to grab those query parameters and parse them into your route in your sort
of parsing function that you write as a user, pulling off values from your app URL record,
what would that look like?
There are two different use cases for query parameters, in my opinion.
Either you have just a couple of them that you support.
You could have one called sort to decide in which way a product listing is sorted and
one called size, if you want to filter your products by size and so on.
Or you could have a more dynamic use case where you kind of want to take all of the
parameters as a dictionary, look them up dynamically according to something defined in the backend
or whatever.
So that latter use case where you take all of them, that's quite easy.
You just take URL dot query parameters and then off you go.
But if you just want to pluck off a couple of them, then in this function where you do
your pattern match on the path, we talked about what you should return.
Should you return a route or should you return a maybe route?
And I recommend returning a maybe route because that fits really well with doing dictionary
lookups in this query parameters dictionary.
So then you could do like dic dot get and then the name of the parameter like sort or
And then you say URL dot query parameters and then you get maybe list of strings because
there's nothing stopping you adding like multiple sort parameters in the URL.
And then my recommendation is to always pipe to maybe and then list dot head, which is
a somewhat convenient way of just deciding that, okay, we're going to take the first
one in case there are multiple.
Now finally, I have this maybe string, which is like the sorting order or whatever.
And you can choose what you want to do with that string.
You could like store it in your route or page type, or you could parse it further.
You could switch on it or case on it.
Is it the string descending?
Then it's okay.
Is it ascending?
It's okay.
If it's something else, then you could like decide to drop it or whatever makes sense.
You could turn it into a default value.
If you needed a default value, you could take it from a string to a custom type if you wanted
to have ascending and descending as custom types.
And in which case you have to say, if it's something I don't understand, then probably
go with the default option because URL parsing is a little bit interesting because the concept
of parsing implies that something could fail to parse, in which case there are some errors.
But there's not really a way to show errors to the user.
At least that's not usually what you want.
Like if you say sort equals D, but it's supposed to be D-E-S-C, you probably just want to ignore
Although I suppose if you wanted to like parse into a result type and say something that's
wrong with the error, you could.
But I guess the way users are using the URL is they're usually not hand editing it.
So you don't necessarily want to give them error feedback.
They're like clicking links.
So you just want to assume they're probably going to have a valid URL and just fall back
if not.
It's only power users that edit URLs, I think.
So I think it's fine to drop bad values and like arbitrarily choose to take the first
one if there are duplicates, because power users will like understand that, hmm, okay,
it's ambiguous if I do two of these.
So it doesn't really matter what you do.
It is kind of cool how this pattern is like, the code is so simple and easy to understand.
Like in my opinion, it's easier to understand than an Elm URL parser.
Not even thinking about like the parser type with the two type variables, like the code
itself is easier for me to follow.
But at the same time, it's more powerful.
Like for example, if you wanted to parse it into a result type and give some error with
messages saying, you know, sort was an invalid value, and then send that in your bug tracker
to just say like, just for your information, this is something that went wrong in the URL,
and then fall back to no URL, you could do that.
Whereas I wouldn't know what to do with the Elm URL parser for a more sophisticated use
But like really, my imagination just starts firing at all these possibilities with this
simple pattern.
I remember that with Elm URL, you can put parsers for the query using the operators.
So you have this less than question mark, greater than symbol or operator, and then
you can put a query parser, right?
And I'm guessing that you can map that one, you can use parser and then, and that means
that you can fail the query or the felt this parsing, if it doesn't match descending or
ascending, right?
Yeah, that's totally possible.
But the thing is, usually you do parser that one off.
So if this thing fails, then it just goes on and tries a following parser.
And in which case you don't have an error message, because you just says, okay, well,
this one doesn't match.
Let's go to the next one.
So yeah, with with an Elm app URLs approach, it is easier to make nicer error messages,
if you want to.
I could also imagine I'm trying to think of a use case where you would want this, but
I could imagine combining certain combinations of query parameters with with path segments
to say, these two combinations, go together and kind of pull out the the maybes from from
types and turn them into more nuanced variants that they tell you exactly the types that
go together.
Of course, like, again, the problem is that you don't want URLs to be able to fail easily,
you want them to be very resilient and fault tolerant.
So usually, we, we actually do end up having maybes often in things like query parameters,
because the segments need to match us, we tend to be strict about segments and say,
hey, if, if you go to slash products with a Z, instead of products with an S, then we're
not going to handle that.
But with query parameters, you want to just gracefully fall back to default.
So yeah, for instance, I can imagine that in some cases, that's not true.
For instance, like we like to say product slash and then a product slack or product
ID, but you can also have imagine that there's a query parameter for the product ID or for
a user.
So imagine you have slash user, question mark, ID equals something.
And that like, for some reason, like maybe legacy reasons, because this needs to support
some URL pattern that was meant that was designed years ago, like this ID should never be absent.
So if you don't have it, then you probably want to lead the user to an error page saying,
hey, the ID is necessary.
And that would be easier with Elm app URL compared to Elm URL.
So another thing, I've thought a lot about URL parsing stuff in the context of Elm pages,
Elm pages has file based router and, and Elm, Elm pages supports sort of like splat routes
like catch all routes and, and optional route segments.
As I was designing, you know, the approach for that, one of the things I was looking
at with like the Elm URL parser was, well, how would I do catch all routes.
And what I ended up realizing is that it doesn't support catch all routes.
And so essentially, like under the hood for for the file based router I built into Elm
pages, I was not able to use Elm URL to parse the URLs because it didn't support what to
me seems like this very common standard use case of having a catch all route like having,
you know, slash repo, user slash repo slash branch slash blob slash some file
path, which is a, an arbitrary number of segments that seems like very normal.
But with your pattern in Elm app URL, it's pretty trivial, you just use these standard
pattern matching tools of list pattern matching.
That is a good point.
I'm not sure if I've ever made an app with like any number of segments at the end or
But now that you say it, I wouldn't know how to do it with the URL parsers.
I think you would have to go back to the URL type, right and do it yourself.
Yep, that's that's a that's a good experience.
Yeah, I actually so I think I saw Simon posting something maybe this was like the seed of
this design for you.
But I think I saw you posting in response to somebody's question on slack about Oh,
you could actually like, handle URL parsing with this simple trick, you could just do
a simple pattern match.
And I was like, Oh, yeah, you could couldn't you.
And then I quickly went in and cut out all of these regexes from the Elm pages generated
code for URL parsing and just turned it into a pattern match in the generated code.
And it's great.
So thanks for that.
So one thing where I think Elm app URL is maybe not as nice or like, it chooses a different
way of doing things.
In Elm URL, you could have segments, which are parsers, right?
And you can map them, right?
So you just like JSON decoding, you can map it, you just like maybe you can map it.
And for instance, you could have a custom type for a product ID or product slug, which
would not just be a string.
And that you would have to do yourself in the patterns in the pattern matching, right,
you would extract the product slug, and then it's just a string.
So you would need to not forget to convert it to a product slug.
That is correct.
Except that you can't really forget it, since it won't compile if you exactly type.
But yeah, you would need to do it wrongly in a few places, like in the type in the route.
And yeah, so that is a good point.
Yeah, I don't think it's a big deal breaker at all.
Like, yeah, not at all, actually.
My take on it is that so Elm URL, it has parsers for string and int by default, and then you
can make your own.
And in my experience, I use string almost all the time, int very rarely.
And when I use a string, it's usually like, this is supposed to become a product ID or
a product slug or a user ID or something.
And they are like, opaque anyway, I don't need to like do much to turn a string into
a potential product ID.
All I need to do, basically is to wrap it in a type.
And of course, that might not be a valid product ID.
But you'll notice that very quickly, because the first thing you're going to do on your
product page is trying to fetch that product.
And if that gives a 404 from your API, then you need to display like a product does not
exist page.
Yeah, wait a second, like, so if you say that the product slug or product ID is an integer,
and it starts with 000, and it's like 0001234.
And you say it's an int, then the product ID is 1234 and not 0001234.
So when you go out and ask the server, like, can you give me the product, it will say like,
I don't know this one.
I know one that starts with 000.
But I don't know 1234.
So that's like, kind of scary, actually.
Actually unsure what the int parsers in Elm say about leading zeros.
But the point here is that if you are typing your IDs as ints, then you could ask yourself,
why am I doing that?
Why does it matter that it's an int?
You're not supposed to do math with IDs, you're supposed to like just check are things equal
and stuff like that.
Unless your IDs are sequential, which for security reasons, you shouldn't do anyway.
Unless they're Pokemon.
The Pokedex number is a meaningful number.
Fair, fair.
Should we go ahead and talk about the third of my issues list, which is the escape incident.
The escape incident.
Is this also related to Jurassic Park?
Yeah, the dinosaurs escape.
That's true.
So in the Elm URL package, there is a module called URL.builder.
And it has functions like the one I use the most called absolute, and there are like relative,
I think, and cross origin and stuff like that.
But they're pretty similar, all of them.
They take two lists.
One list is for the path, and one is for parameters.
And if we just focus on the first list, which is the path, then you can write something
like the string A comma the string B.
The result will be slash A slash B, which is quite handy for generating URLs.
But if you type a slash inside of one of those strings, what is going to happen?
What should happen?
What should happen is it should get URL encoded.
But I'm guessing that's not what happens.
It is not.
And that is usually fine.
But it can also result in some really weird code.
I've seen people using this absolute function where they have written one segment as a string,
like A by itself, and then the next hardcoded string, B, and then maybe comma and a variable,
which is something dynamic, and then comma, more hardcoded things.
But this time, they chose to use slashes inside the string instead.
And at the very end, they even do plus plus something other dynamic, which is like now
you have mixed all the different ways that you can.
Oh, I don't like that.
But that's just, it's fine.
It's just confusing code.
But it could have more severe consequences if, for example, if you have like a URL slash
blog slash a slug of a blog, and your blog post is called like A B testing.
And that's usually written as A slash B testing and your slug function or whatever, allows
slashes in a slug, which might be pretty uncommon, but let's pretend that happened.
If you then try to create a URL, and you put that slug in there, then you're accidentally
gonna create a URL with three segments instead of two like you expected, which probably results
in a 404 if you try to use that link.
So the approach I wanted to take with the app URL is that it should feel more like the
HTML package.
When you put in a string in HTML in Elm, I never think about what that string looks like,
like, will there be less than signs?
Will there be ampersands?
Could this be treated as HTML?
No, it never happens.
So I make sure that you can put any string anywhere, and it will represent that string
exactly and not be treated as URL syntax.
Okay, so if you if you put a slash in A B testing, then the slash would be escaped as
a percentage sign and something else.
Okay, that feels very Elmy to me in the best possible way like it because to me like the
feeling of working in Elm that I love is number one, like, not feeling like there are foot
guns all around me that I'm going to set off by mistake, like just that peace of mind of
using something and being like, yeah, it's going to be fine.
And then secondly, when I'm trying to like follow a code path and understand what it's
doing, or what it might do, or what might be causing a bug, having fewer places to look
for, for the source of the bug, fewer special cases to think about, or like fewer possibilities,
like the type is more narrowed, or what this can possibly do is more narrowed, or side
effects can't be coming from here.
So this is like, could this segment that's coming from user input or something be causing
this weird URL thing?
Like, no, it's like, it's going to be escaped.
So you can just cross that off the list of things to look for for a specific type of
problem from.
Yeah, very, very nice.
And this escaping stuff was actually useful at work.
We had a bug, like before we used app URL, where we have like a search function in our
And you can search by a person's phone number, for example.
And someone wanted to search for their like full phone number with the counter code, which
starts with a plus.
So in Sweden, it's like plus four, six, and then the real phone number.
And that always gave zero matches.
And we were like, why doesn't that work?
What's happening here?
And when you, we send the phone number in a query parameter, and we were using the standard
elm slash URL, like URL dot builder dot absolute.
And then the second list of that function, you can specify your query parameters.
It does not escape the plus.
And for some reason, only in query parameters, a plus means space.
So our server, by default, just unescaped that for us and says like, oh, it's a space
for six and the phone number.
And we don't have any phone number that starts with a space.
So we had to like in the elm code go and manually use the URL dot percent and code function
to make sure it's escaped.
But now with app URL, we don't need to think about it.
We could like remove that and just put the phone number in as is and it's going to work.
So when you mentioned that there's an absolute and a relative function in your builder, you
said the second argument is a list.
And I was like, why is it a list?
Because the in the URL, the query is just a string, right?
So why for query builder, do we have a list?
So it looks like it's taking a list of query parameters.
And those query parameters are basically key values, like you can say string, which with
a key and a value, which is a string, or int with a key again, and an int value.
So like, it's weirdly, the writing of the building of a URL doesn't match how it is
So that's kind of weird.
I agree.
Parser and builder are very, like almost different universes in the same package.
So the URL builder is like, oh, yeah, sure.
Of course, queries are like, they're a dictionary and for parsing, like, no, it's just a string.
People can do whatever.
And the fun thing with a parser is that, like when you run a parser, you give it a URL,
the URL type.
And the first thing it does is pre-process the query parameters into like a dictionary
kind of structure.
And then the parsers that you write, like work on that pre-parsed type.
So it's like, couldn't really decide which way to go or I don't know.
And also when you when you think about the URL builder in the elm-url package, and the
way you create these query parameters, there's like a query parameter type, which you have
a list of.
And if it's a string, then you give string with the key and the value.
If it's an int, you give an int with the key and the value.
But so what is the like query parameter type and the int and string builders for the URL
type, query parameter type giving you?
It's really giving you string.fromInt.
That's what it's bought you.
So like, it seems so much more natural to just remove that level of abstraction.
It feels kind of similar when I use like the headers in elm-http.
And you build up a list of headers, but you have to do a header constructor to do, you
know, http.header and give it the key and value.
Why can't I just give it a list of tuples?
Like that would have the same effect.
So it just seems like, again, the Jurassic Park principle here.
It's like, you could do that, but what value is it really giving you?
And it's that much more that you have to like, look up and hold in your head when you're
navigating this API.
So I really like the simplicity you've arrived at.
I'm very curious.
I'm sure it's difficult to answer as a package author because you do your best to not have
any cons.
But we've talked about a lot of pros.
Are there cons?
Are there downsides?
Are there common use cases that your package might not be a good fit for?
I think that if you use query parameters like a lot on every page, quite a lot of them,
it might be annoying to work with a dictionary type that I went with.
And my reasoning when designing the query parameters was that like in none of the apps
that I work with, we have that many query parameters.
So it was hard to come up with something nice because I use it so little that like just
a little bit of extra code here and there was simpler than trying to figure out a nice
API for working with them.
So basically the thing that I'm the most excited about in the package is the pattern matching
on the path pattern, which is easy with this package.
And then the query parameters that I just wanted to like, here is a structure that represents
them quite well.
And it's kind of easy to work with, but it might require a line or two or extra of extra
code here and there.
Yeah, I mentioned that you rarely use query parameters in practice, but also even less
multiple times the same one.
So you have a dictionary of list string as values, but in practice it's always just a
single value, right?
That must be the most common.
Yeah, sure.
I actually really like this thing I noticed happening a lot in Elm where something that
like in other language ecosystems, I noticed tends to be like a library to help you do
In Elm, sometimes it's just, well, it's actually pretty straightforward to do it.
And it ends up being like a pattern rather than a library.
And really it feels like that's what Elm app URL is.
It's like a, it's three functions to help you use a pattern, but the pattern is the
bigger thing.
And if you wanted to, if you have very heavy processing of query parameters in your application,
you can create your own domain specific query parameter parsing API and use that with the
dict string list string.
So it's like ultimate flexibility and simplicity, and you can build your own thing to, you know,
to address your own internal needs if you outgrow what this pattern allows.
Speaking about patterns, there is one pattern I would like to bring up, which is dry, the
dry principle.
Don't repeat yourself.
I've learned something there when using this package and that is to don't worry too much
about repeating yourself in this case.
What did you say?
Don't worry too much about repeating yourself.
Oh, now I did it.
Did you set me up?
But did you worry about it?
You didn't worry.
You're good.
Oh, nice.
So it's very common that you have URLs that all start with the same pieces.
Like you could have slash product slash slug.
And on top of that, you could have like slash specs, slash reviews, slash whatever.
And for reviews, maybe have like slash reviews slash and then review ID or something.
And you might be tempted to like write your code so that you mentioned the string product
just once or like you mentioned the whole piece product slash slug slash reviews just
once so that if you need to change that URL, there will be just one string to change.
But when I have written my code like that, like nested pattern matching or like having
sub functions for parsing deeper and deeper, that's so much harder to understand and results
in so much more code.
So what I like to do is to simply type out every pattern in my pattern match, even if
it means I write product like 10 times, it's so much easier to read.
And you also get this like nice overview of all your pages in one place.
And what I'm thinking here is that you need to repeat it once anyway, because you're going
to need to be able to create these URLs as well.
So you can't just mention product, the string product in a pattern match, you also need
to have it in a function that can create those URLs.
And on top of that, like, how often do you change your URLs?
It's usually an anti pattern to change your URLs, but because people are going to have
them in their history and stuff, and if they don't work, your, your app is broken, right?
Yeah, if you're if you're really worried about it, maybe write some unit tests for it.
There's someone named Arlo Belshi has a concept he talks about, I'll link to the blog post
where he makes the argument that for for tests that you write, people overuse the concept
of dry, and he proposes for testing using a concept called wet, which he he says is
write explicit tests.
Because sometimes what happens is, you almost need a test for your tests, because you use
so many levels of abstraction for writing your tests that you don't know if it's actually
doing what you expect.
So his argument is, you should be able to look at a test and know exactly what it's
And having extra boilerplate and repetition is not a bad thing if it makes it very easy
to understand exactly what it's doing without having to second guess it.
And I think a similar concept would apply here.
Don't be too clever, you know, and especially don't be too clever where it doesn't matter,
which URLs is a good example, in my opinion, because like, how many URLs are you going
to have in your app?
In every app that I worked with, it's like 10 or 20 or 30.
But we don't add a new one like every day.
So to me, it makes sense to have like a dumber, simpler solution for such things.
Yeah, actually, Elm has changed my understanding of what maintainable code looks like.
And I something is stuck with me that I heard Richard Feldman say about, like, how we talk
about boilerplate in these things.
And of course, like, there's certain types of boilerplate that make code harder to understand
and maintain.
And that's not good.
You know, you want code to be very straightforward and clear and easy to understand.
And you want things to be less error prone and fewer places for human error.
But boilerplate that doesn't have the possibility of human error that doesn't introduce another
possible point of failure is not such a bad thing.
It's not necessarily what's slowing down your ability to maintain code.
And so that's kind of stuck with me is like, when you're trying to simplify something,
you have to think about like, why are you simplifying it for some aesthetic purpose
that doesn't actually change how easy something is to maintain and work with and how bug prone
it is or not, you know, so I think that's a that's a really good nugget of wisdom.
What really surprised me with this solution with LmapURL is how simple it is.
And then I'm wondering, like, why did we put up with LMURL's approach for so long?
Like, I mean, it works.
So it's not something that we have to reinvent on purpose, because maybe it works pretty
But like, how, how come we stuck with it for so long?
I think the key here is that the elm slash URL package works.
There are like some annoyances with it, but it doesn't stop you from doing what you're
supposed to.
And once you are a bit familiar with elm, like you figure it out, you're able to set
up the URLs you need.
And then you don't think about it and move on with more interesting things.
And also, it's a core package, right?
So, oh, it has to be good or this has to be the way.
I guess that's like judging by the quality of elm itself and all the core packages, you
have that expectation on all of them.
But if you think about it, like, of course, there's going to be one package that isn't
as good as the others.
It's like impossible to be perfect in every single one of them.
And it took us long to realize, I guess.
But I bet that beginners might appreciate a simpler way because that is like one less
hurdle to do an elm application.
So in your opinion, should we get rid of the two operators that elm URL defines?
I think that if you take the stance that there shouldn't be custom operators, then there
shouldn't be any operators to import from any package.
It's like a weird extra thing to learn that, oh, wait, what?
In the URL package, you can import operators?
What does that mean?
Like, it's so foreign.
You're not used to from other languages that you can import an operator.
They just exist.
And it feels like if we're going to have extra custom operators, don't waste them on URL
Like, you could choose anything and you chose URLs.
That's not what I would do.
I'm guessing the only remaining ones that are not in the basics module are the ones
from URL parser.
Just parser.
Just parser.
Elm parser.
Which I've always thought kind of trips people up a little bit.
And I've always thought, how about keep and skip for those two operators?
Because it says what it does explicitly, and it reduces having to explain some cute thing
about an animal like eating your parsing input or something.
I just think it would, I think it reads more easily and intuitively, and it would kind
of ease the learning curve there a bit.
And it would reduce the number of custom operators in published packages.
Also like just the, sometimes you use pipes, and sometimes you use those symbols that look
like pipes, but the way that you have to move those instructions, like that there, it's
a bit weird.
Like, oh, if you now want to keep this, then you need to move things around.
I found it pretty confusing when I was working with parsers at least.
And also like if we can remove the importance of parsers, of operators, that would make
my life easier with Elm Review.
So I'm fine with that change.
So is there where you're going to tackle next parsers?
To be honest, like I've used the Elm slash parser package exactly once, I think, in like
one Advent of Code puzzle, where I didn't even need it.
I just used it because I wanted to try it out.
When it comes to like real applications, I've either been using other people's parsers from
packages on the packages site, or I've just gone over, or I've just like used a RegEx
or string splitting or something, and that has been just fine.
Actually RegEx might be next on the chopping block, in my opinion, of something.
I think there's a saner way to do a RegEx API in Elm.
That's something in between needing a full on parser package and needing to use some
kind of loosely typed wrapper around the JavaScript RegEx API.
I have some thoughts there.
That's for a different episode.
That might be for a different episode.
But Simon, this is a very interesting package, and it's really unique in the sense that it
is seems like far more design attention and documentation and examples than code, which
is kind of cool.
What was it like designing this package?
Was it just having a bunch of open tabs and doing a bunch of reading and research?
A lot of that and a lot of, I mean, it actually took many months because I started using it
or like trying it in a branch at work.
And then every time I was like, no, now I have the API nailed.
And then I tried to use it and like, nah, this was a bit annoying.
Or like, I didn't think about this use case.
So I think the key was to actually use it in several real projects to learn what is
important and what is easy to use.
Did the scope of it and the amount of code shrink over time?
I get the sense that maybe you realized how simple it could be over time, or did you kind
of have that sense from the beginning?
It changed a bit over time.
I like removed some query parameter helper function that I realized didn't really improve
And I got some feedback on Discord and that helped a lot too.
For the query parameters, was it like piping into list.head?
That idea that how do you get like a single value for the query parameter?
Yeah, I was debating if I should have a function for getting just a single value since that
is the most common use case.
But it was very difficult naming it and also deciding which way should it work.
Should I take the first one?
And I looked at like some other programming languages.
I think I don't remember which one it was that took the first one, maybe Go?
Or should I take the last one?
That's what Django in Python does.
Or there is actually one more option, which is what elm.slash.url does.
If there is more than one, you get none of them.
I'm not sure like what the intention is, if it's a mistake or if it's like, this is ambiguous,
you get none of them.
Right, it failed to parse.
That, see, the thing is that if you can present error messages, then that makes a lot of sense.
But when you can't, like it seems like one of those instances where you want to be, what's
the what's the phrase, you know, like accept a wide range of input, but then be very precise
in how you build URLs in your application, but accept a wide variety of possible inputs
with possible oddities from the user.
Something I realized was that if no one really has noticed that for so many years, I don't
think it matters which one you take.
You just choose one approach, and that's going to be fine.
But in the end, I decided not to make that decision in the package and just promote the
pattern of using list.head instead.
Honestly, if I see a query pattern that is duplicated in my URL, I consider that to be
a bug.
Like, if I see one, I'm gonna like, yeah, we'll probably need to do something else than
that, like, separated by comma, whatever.
Yeah, and you have the added benefit of not having an O of N query parameter lookup if
you do the last one and someone put 50 query parameters of the same name.
So that's good.
It's a DDoS attempt factor.
Well, Simon, what should people look at if they want to get started?
I made a little video that very quickly explains the core concept.
But other than that, you can go to the package on the package site, the readme, like, shows
you the main thing immediately.
And then I have some examples for every function as well in the documentation.
Well, thank you for the thoughtfully designed package.
Thank you for the conversation.
Thanks so much for coming back on the show.
Thanks for having me.
It was great.
And Jeroen, until next time.
Until next time.