elm radio
Tune in to the tools and techniques in the Elm ecosystem.
Wrap Early, Unwrap Late
We dive into preventing bugs and making your code more maintainable by wrapping early and unwrapping late.
Published
June 20, 2022
Episode
#59
Primitive Obsession episode
Parse, Don't Validate episode
Dillon's
Entry Gatekeepers
and
Exit Gatekeepers
blog posts
Dillon's talk on
semantic types
Transcript
[00:00:00]
Hello Jeroen. Hello Dillon. Well it's just you and me today, no Martins. Just you and
[00:00:10]
me. So it's an Elm Radio classic. What are we talking about today? Today we're talking
[00:00:17]
about Rap Early and Rap Late, something that you've mentioned quite often and in which
[00:00:22]
I would attribute to you as far as I know. Yeah I think so. But it did attribute like
[00:00:29]
all the tiny steps ideas to you and that turned out to be not you. Oh yeah that is very much
[00:00:36]
ideas that I borrowed from, generously borrowed from Kent Beck and other thinkers on that
[00:00:43]
subject. Yeah so Rap Early, Unrap Late. So I have a lot of thoughts on this topic Jeroen
[00:00:52]
but I'm kind of curious if I could put you on the spot a little bit. What do you think
[00:00:57]
of when you think of Rap Early, Unrap Late? Can I just cheat and say opaque types? It's
[00:01:05]
always a safe bet. It's always a safe bet here. I think of decoding and I think of wrapping
[00:01:14]
values into new types so that we don't pass them in the wrong locations. Stuff like that,
[00:01:21]
just to add more guarantees. So that's for the Rap Early part and the Unrap Late is more
[00:01:26]
for manipulating data the way that you would like to until you send them over to a server
[00:01:34]
request for instance. But I'm not trying to make a nice definition here so we should,
[00:01:40]
I think you should provide your definition after you say what you think of my thoughts
[00:01:46]
here. Yeah I mean I think those are some good thoughts. I think we'll get into a lot of
[00:01:52]
subtleties of it. So yeah, rapping like you say, it has a lot to do with constraints and
[00:01:58]
I think semantics. I think we touched on this subject in our primitive obsession episode
[00:02:06]
which I definitely recommend people go back and listen to if they haven't heard it. There's
[00:02:09]
definitely a connection between these two topics of primitive obsession and Rap Early,
[00:02:15]
Unrap Late. But you know as you say, the rapping, it's about getting your constraints in
[00:02:22]
order. You know one of the things that I think is a defining feature of the Elm way of coding
[00:02:28]
in my eyes is rapping things at the gate or dealing with constraints at the gate like
[00:02:35]
decoding. Like if you decode JSON or flags, you don't lazily find out that there's an
[00:02:42]
error. You sort of like find out about problems right away. It makes me think that it's funny
[00:02:49]
because when you say, when you talk about gates, I'm like okay yeah you talk about ports,
[00:02:54]
you talk about HTTP requests and stuff like that. But if you talk to a JavaScript developer
[00:03:00]
and you talk about well you want to rap things at the gates and you're like what gates? Exactly
[00:03:07]
right and so there are challenges with TypeScript because TypeScript can describe what a type
[00:03:15]
is. But it has a lot more trouble giving you certain constraints that relate to like how
[00:03:22]
you can create that type because it's sort of a casting oriented system where I mean
[00:03:30]
you know you can just do JSON.parse and it has any type and then you can cast something
[00:03:36]
that has an any type or you can cast something to any. You can say this value as any and
[00:03:42]
then you can cast that to some value right? There are some validation packages out there
[00:03:50]
that pretty much do what Elm decoders do but you don't have to and you're not forced to,
[00:03:56]
you're not required to. Exactly right. That's right you can do those things but at the end
[00:04:02]
of the day TypeScript is a system that is designed to allow you to cast things if you
[00:04:08]
want to and Elm is a system that has no concept of casting and that is an essential difference
[00:04:15]
because it means you can really trust where something came from. So to me a big part of
[00:04:22]
this rap early piece of rap early unwrap late is where something came from and kind of having
[00:04:29]
a stamp of something that says this is the code path which as you hinted at has a lot
[00:04:35]
to do with opaque types too because if you expose a way to construct a type outside of
[00:04:41]
the module it's defined in then anybody can create it. Just like in TypeScript you can
[00:04:46]
cast something into a type anywhere you want and so you don't really know where did this
[00:04:53]
value come from, what does it represent, what constraints does it give me. It's harder to
[00:04:58]
enforce those things. So you also have a harder time figuring out has this been validated
[00:05:05]
somewhere along the way.
[00:05:07]
Exactly. Yeah I think of it like you know if you go into a beer garden and you get a
[00:05:13]
little wristband that shows that you're of drinking age and then you can go buy beers.
[00:05:18]
Is that how things work in the US?
[00:05:23]
Sometimes yeah. We're very strict about making sure that people are of drinking age. I just
[00:05:31]
recently saw someone who was like clearly in their 60s or 70s getting ID'd at a store
[00:05:36]
when they were buying some liquor and you're like really? Really? Okay.
[00:05:40]
I know that in the Netherlands they have something that says if you're above 25 you don't need
[00:05:48]
to show your ID which I just don't understand. Like how can we know you're above 25 if you
[00:05:57]
don't show the ID? I have never understood this one.
[00:06:00]
Well it's a slippery slope right? Then you say how can you know if they're above 60?
[00:06:08]
Yeah.
[00:06:09]
Yep. But yeah you get that thing around your wrist and then you can trust where that came
[00:06:16]
from because the only person that has those wristbands is the official place that's handing
[00:06:25]
them out and you know that they're checking your ID and you know that if you take it off
[00:06:30]
it will break so you can tell if you've tried to give it to somebody else.
[00:06:37]
So you're sort of like getting this stamp of approval that you can trust and that's
[00:06:43]
important and that's the wrapping part. So that's wrapping but the early part is important
[00:06:51]
too right? And we talked about primitive obsession right? And that kind of gets into what's the
[00:06:57]
ideal representation of a type and what constraints do you want to model about a type. But the
[00:07:03]
early part is really important here right? Because why early? Well if there's a constraint
[00:07:09]
or if there's semantic meaning to something you want to find out as soon as possible.
[00:07:14]
There are a few reasons for that. One is you don't want to keep checking that. If you keep
[00:07:18]
checking then it would be a little bit like every time you go to get a beer you check
[00:07:24]
your ID and that's kind of annoying because maybe you want to get a few beers. So it would
[00:07:29]
be nice if you could just check it once and you don't want to make people waiting in line
[00:07:34]
to get their beer wait for that person. So you know it's a matter of convenience to not
[00:07:39]
have to keep rechecking it constantly.
[00:07:42]
Hey could I get five beers? Yes could I see your ID five times?
[00:07:49]
Yeah.
[00:07:51]
Yeah it's inconvenient. So that's one reason for the early part. But another reason for
[00:07:56]
the early part is that it has to do more with safety. Like you want to know that the thing
[00:08:03]
represents what you're checking for and the sooner you check for that the safer it is.
[00:08:10]
The fewer opportunities there are for that thing to start to diverge and you to start
[00:08:15]
to lose track of what the thing meant. So if you have a if something comes in as a string
[00:08:22]
and you're like I think this represents a social security number or I think this represents
[00:08:28]
a username. Well the farther it drifts into your code base number one the more opportunities
[00:08:35]
there are to use it in a way that you that is incorrect or unsafe or undesirable.
[00:08:42]
So for instance passing the email as a username and the username as the email for instance.
[00:08:48]
Exactly and passing a social security number to be sent to a place where it's going to
[00:08:55]
be displayed without being masked or so it's harder to. So there are more potential places
[00:09:02]
where you can use the type in an unsafe way. Either that the type has a certain way of
[00:09:11]
being used that you want to honor like a social security number not being displayed in certain
[00:09:15]
contexts or a password and you want to make sure it is the thing that you said it was.
[00:09:21]
So that's number two right is like the closer you are to the point where you receive the
[00:09:26]
thing the less likely you are to mess things up. Yes because so if you do like decode field
[00:09:32]
username username decoder so decode field string username username decoder you're saying
[00:09:40]
I'm getting this thing this JSON value that represents the username it's called username
[00:09:45]
so I'm pretty sure I'm doing that right I'm not mixing it up and then I'm doing username
[00:09:51]
decoder it directly wraps it into a username at that moment so it never exists in the code
[00:09:57]
base as a string if it did if you did decode that field username decode that string now
[00:10:04]
you're decoding it in as a string and a little bit later in the pipeline you're going to
[00:10:09]
say oh let me wrap this into a username value to say that this represents a username and
[00:10:15]
to to validate that it is a correct username well again like it could drift it could you
[00:10:23]
could mistakenly mix it up with something else you could pass it to a place that you're
[00:10:28]
expecting to have an ID but you gave it a username you could give a place that was expecting
[00:10:32]
a username an ID and also again just like you know giving your ID to to every vendor
[00:10:41]
that you're buying a beer from you you're having to recheck that validation over and
[00:10:47]
over again more places than you need to so so that's the you know the wrap part again
[00:10:53]
it's about what are the constraints that we want for this type what does it represent
[00:10:59]
how can it be used what what do we know about it and then the early part is you want to
[00:11:07]
do it as soon as possible so that it is not incorrectly used and so that it is we're more
[00:11:14]
confident that it is what we say it is because we don't have it floating around as a sort
[00:11:20]
of primitive type that we're piecing together where we've lost context so so that piece
[00:11:25]
about the the later you go the more opportunities there are for losing context and that's why
[00:11:31]
i think the early part is really important and then there's the unwrapped part and in
[00:11:35]
between the moment you wrap things and that you unwrap things you have all the guarantees
[00:11:41]
that you have with opaque types with non primitive types so you can yeah you can do whatever
[00:11:47]
you want with it yeah and and you know again that i feel like we need that you know that
[00:11:54]
meme of there's a meme about opaque types pretty much it's like this doobie do where
[00:11:59]
they're pulling off the mask and they're like aha it was this person all along and then
[00:12:03]
they they write what it was it's just like elm radio episode pulling off the mask is
[00:12:08]
like aha it was opaque types all along but yeah it does have a strong relationship with
[00:12:14]
opaque types because opaque types is just this fundamental tool in elm for defining
[00:12:22]
constraints right and this is a place where we want to define constraints and we where
[00:12:26]
we want to understand what code paths are possible with a certain type that's opaque
[00:12:33]
types that's like this very very important very powerful tool for that that again like
[00:12:39]
if you expose the constructor then you lose that you lose that guarantee you can't track
[00:12:46]
the code pass that some that a type might go through it's just far less constrained
[00:12:50]
but um but yeah the unwrap late part is again important because you don't want to incorrectly
[00:12:57]
use the type and if you unwrap it and have a string floating around again you could have
[00:13:02]
this social security number or password floating around as a string and you know what what
[00:13:07]
you see sometimes is you know you'll have like a user badge view function that it needs
[00:13:14]
a username it needs this type of data it needs an id and if you're sort of wrapping a string
[00:13:21]
to pass those functions into the user badge view that's a red flag right that you're you're
[00:13:26]
not wrapping uh you're not wrapping early or you're or you're unwrapping early you're
[00:13:31]
unwrapping too early or you're wrapping too late right if you're passing the primitive
[00:13:36]
values you're passing the primitive values and you're wrapping them just in time to send
[00:13:42]
it to a function that says it needs a username this needs a username value and you use the
[00:13:47]
username constructor with your username string value that you happen to have or a hard coded
[00:13:52]
string value that you happen to have that's a red flag that you are either wrapping too
[00:13:58]
late or unwrapping too early and ending up which unwrapping early you know ends up being
[00:14:06]
similar to wrapping late right that you're you end up with this sort of primitive value
[00:14:12]
floating around that has lost the constraints the safety about how you're supposed to use
[00:14:18]
it and the semantic meaning unwrapping is always when you need to use the value not
[00:14:25]
for logic but to send it to some external system to elm so that would be showing it
[00:14:32]
as html sending it to the virtual dom sending it on a cdp request graphql request or sending
[00:14:38]
it over to a port you should not need to use you should not need to unwrap in other cases
[00:14:46]
right i think your list there was pretty exhaustive as far as i can think of those would be the
[00:14:54]
those sort of boundaries those terminal points so it basically you yeah how do you know that
[00:14:59]
you're doing unwrap late late enough well it happens at a terminal point and as you
[00:15:06]
said like that that's a very good sign that you're doing it at a terminal point because
[00:15:10]
you send something through to a port and you're like well now i have to get some primitive
[00:15:15]
value because i can't send this custom type through this port or you're sending an html
[00:15:22]
request and hey like unfortunately i can't send this html request over the wire unless
[00:15:27]
you know it's something like lame darren you do send to back end then you can you can actually
[00:15:33]
unwrap even later even though technically there is something happening under the hood
[00:15:37]
this is actually a very important feature of lame darren is you can trust the code the
[00:15:45]
code path of like the data that that thing represents for even longer and unwrap it even
[00:15:51]
later yeah because you don't you don't have that intermediate data that's it's sent over
[00:15:56]
the htp or web sockets or there is but you don't see it you don't have control over it
[00:16:03]
so you can't mess it up right although there is maybe um something worth noting i i actually
[00:16:11]
haven't really thought about this so much or or heard this talked about but with lime
[00:16:16]
dira you still i think do have to assume a potentially antagonistic client right like
[00:16:23]
you can't you you can't trust the client no matter what that's just um not possible right
[00:16:29]
so if you say well if i have a username then it means that it ran this check that it validates
[00:16:37]
and it doesn't have these symbols right and in in lame dara if you say okay send to back
[00:16:43]
end username and then that gives you this confidence that this thing has gone through
[00:16:49]
this check it's passed through this gate it has this band that says somebody checked your
[00:16:54]
id and you're of drinking age right well you know you you you can trust that on your client
[00:17:02]
side code insofar as that matters as if if the client wants to break those guarantees
[00:17:08]
by hacking some client side code then there's nothing you can do about that but you want
[00:17:13]
to make sure you have a bug free experience for the code you wrote right so you can trust
[00:17:17]
it in that regard but as far as trusting when you do lamb dara dot send to back end you
[00:17:25]
can no longer trust the code that it went through and what you receive because it says
[00:17:30]
well it a username cannot have an at symbol and it cannot have an exclamation point and
[00:17:38]
if i do the only way to get a username is if it's passed through that validation because
[00:17:42]
i conditionally return the username type after it's run through that check you actually can't
[00:17:48]
trust that i don't i don't think uh it's possible to trust in lamb dara because somebody could
[00:17:53]
hack the low level protocol and get lame dara to receive that data so you i think you would
[00:18:01]
need to reperform those checks yeah i'm thinking maybe if you can sign the message somehow
[00:18:06]
but i feel like the client could hack that way resign things themselves anyway too so
[00:18:14]
i like that idea i wonder if there is you know theoretically a way to to sign it that
[00:18:20]
that would give you that guarantee but yeah you're right that that would be the only way
[00:18:24]
if it's not signed in some in some way then you have to um you cannot treat those guarantees
[00:18:34]
as going across boundaries because when you receive something from the back end that type
[00:18:39]
may no longer represent the constraints it promised to because it's possible to to bypass
[00:18:45]
that i wonder if in all cases on the back end you could re verify the constraints like
[00:18:52]
checking whether an email is indeed an email well actually email is pretty hard but uh
[00:18:59]
checking whether a security number uh social security number is uh is valid then that could
[00:19:05]
work out but if you need to figure out whether a list of users will that refer to one another
[00:19:13]
that they're all valid that that can be a bit more tricky yeah i mean as a general concept
[00:19:20]
being able to trust that something went through the exact code paths that that it's that its
[00:19:28]
type would imply or the you know that that wasn't bypassed in some way by creating that
[00:19:34]
low level type i just don't know if there's a way to do that across boundaries and it's
[00:19:40]
pretty interesting i mean even with um mts interrupt you you're not able to do that right
[00:19:47]
i feel like lambdari would be slightly safer but still not entirely safe yeah i mean it's
[00:19:53]
more about like because i think people people think about the term type safety i get the
[00:20:00]
sense often in the sort of javascript community there's a lot of thinking about type safety
[00:20:06]
as like this is a string this is a number i can trust those things but to me that's
[00:20:12]
just like scratching the surface of type safety and and to me type safety is much more about
[00:20:19]
you know yes i want to know that a string is actually a string and an int is actually
[00:20:23]
an int but i want to go beyond that and i want to know that a username actually went
[00:20:29]
through this check it passed through this gate i can trust it and not have to do shotgun
[00:20:34]
surgery like we talked about in the parse don't validate episode this parse don't validate
[00:20:39]
concept i want to parse a type have it represent these constraints and be able to trust those
[00:20:45]
and not have to recheck those constraints and types constantly so yeah i mean lmts interrupt
[00:20:53]
is i mean it's getting data from it's kind of bridging the gap between these typescript
[00:20:58]
types and elm types but again inherently like typescript types in a type system based on
[00:21:04]
casting where casting is just a a tool that's given to you it severely undermines what you
[00:21:13]
can trust in terms of constraints for what code paths something went through if it has
[00:21:19]
a given type you just lose that ability to to depend on that 100 whereas in elm you have
[00:21:27]
that and that's that's an important difference now like somebody might say well that's fine
[00:21:33]
but in practice i use some linting rules that say that you don't cast yes but have you seen
[00:21:42]
the disable comments right right and somebody might say like that's fine but i can depend
[00:21:50]
on it enough in practice that it's going to prevent enough bugs and i'm okay with it and
[00:21:56]
that's that's a reasonable opinion that's just not my opinion of how i want to maintain
[00:22:03]
code i love having the hundred percent guarantees and and and that's why i love wrap early you
[00:22:09]
know i'm curious about something uh related to unwrapping so it's something that every
[00:22:16]
time i encounter it i'm slightly annoyed uh it's when i want to send the data over uh
[00:22:23]
an htp request or if i want to uh display it so let's say i want to display the social
[00:22:29]
security number and i want i mean that should be hidden probably right basically my question
[00:22:35]
is should there be a two string function if the underlying data is two string so because
[00:22:42]
because if you send it over to over an htp request then you need to convert it to a string
[00:22:50]
so that it can be converted to a json uh encode value so that you've been sent over htp or
[00:22:57]
if it's displayed as html then it needs to be converted to a string and then it gets
[00:23:02]
displayed by calling html text or appending it or modifying it but but if you have that
[00:23:08]
two string function then there's a slight portion of your code which has access to the
[00:23:15]
raw data and they they can mess it up and now because you have this function the rest
[00:23:21]
of your code base can do whatever with it so they could if they wanted to display the
[00:23:26]
social social security number or the password or whatever so like i know that you can instead
[00:23:33]
of having a two string function you could have a two htp encode value which would work
[00:23:41]
but then potentially ties you to some to some data structure like if if it's a complex thing
[00:23:48]
then you can say oh well uh i'm going to encode it as an object where this field is encoded
[00:23:54]
as under this field and this field is encoded in this field in this format and that would
[00:24:00]
work in one case but not in another and so yeah you're either tying yourself up to a
[00:24:06]
specific format a specific use case or you're allowing the entire code base to to misuse
[00:24:15]
your previously opaque type or you get what i mean right yeah no that's a very good point
[00:24:21]
so i um i actually wrote a post to my mailing list a while back on this but i um and i didn't
[00:24:29]
publish it on my incremental blog but i will um before we release this episode i will publish
[00:24:36]
this post there and i'll include a link in the show notes but it's about so i i have
[00:24:41]
a blog post about entry gatekeepers and i have a blog post about exit gatekeepers and
[00:24:48]
the exit gatekeepers one is sort of these like exactly what you're describing of like
[00:24:54]
how do you make sure that you're using this this opaque type in the desired way and how
[00:25:02]
do you wrap late you know and and basically unwrap late oh thank you yeah unwrap late
[00:25:08]
and so there are two sides of this coin the wrap early part there are techniques like
[00:25:13]
conditionally returning a type when you validate it and things like that right those are some
[00:25:18]
tools you use to to wrap early and also you can define you can define ways of of retrieving
[00:25:26]
the thing if you're um you know you could even uh define a um a command to get your
[00:25:34]
you know your access token or something like that if if you can do that if you can like
[00:25:39]
package it up so that there's this thing that goes and gets this value and you don't have
[00:25:44]
to deal with primitive low level details like json decoding and things like that you can
[00:25:49]
move all of that into your module where you have that opaque type that's ideal but so
[00:25:54]
basically what i try to do is i try to move the logic for both wrapping and unwrapping
[00:26:00]
into that opaque type module so so like in the example of a social security number like
[00:26:07]
you were saying like if you give a two string you sort of are making it very tempting to
[00:26:13]
unwrap early and to just then go and lose those guarantees and you know similarly if
[00:26:21]
you have a from string that sort of casting thing that we are sort of talking about like
[00:26:27]
you know like i was saying with typescript you can just cast a value to this type if
[00:26:33]
you have from string it's kind of similar to doing that you mean a from string that
[00:26:37]
doesn't return a maybe opaque type or that doesn't do any validation good point it depends
[00:26:44]
so it depends on what the validation is if the validation is a username then you're right
[00:26:49]
having a from string for a username is totally fine because if you're saying username represents
[00:26:56]
that this is a valid username i don't care where it came from i just care that it's valid
[00:27:00]
then you're right from string it's totally fine as long as it conditionally returns the
[00:27:04]
thing where it matters is if you have from string for a place that represents what the
[00:27:11]
thing is so like an email address that actually is a valid email address or you know an authentication
[00:27:20]
token that actually is a work working authentication token represents that it's not just like the
[00:27:26]
right number of characters but it actually is one it came from this place yeah you you
[00:27:32]
know that it came from the server or something like that right from string doesn't tell you
[00:27:38]
where a thing came from so ideally if the if the type is supposed to represent not just
[00:27:45]
that this is a valid that this is run through a validation if it's run through validation
[00:27:49]
from string is great if it's trying to represent where a thing came from then you ideally want
[00:27:57]
to have the logic for going and getting that thing in the opaque types module because then
[00:28:03]
the only way you can get that thing is by using that code that's exposed from the opaque
[00:28:09]
type yeah so for one example of that or the actually the only example i have for that
[00:28:14]
is using decoders and that's makes sure that you that it comes from either an HTTP request
[00:28:22]
or a port or that you encoded a value by manually creating some json and then decoding it directly
[00:28:29]
again so you don't have a full guarantee but yeah almost yeah i mean the more of a guarantee
[00:28:36]
you can get the better you know if you if you say like if you have a check that says
[00:28:40]
that an email address is is valid and the only way you can check that is by going to
[00:28:47]
some server and you know sending an email and seeing that it gets approved or or getting
[00:28:54]
in from the database and which has its own checks whatever yeah exactly but it makes
[00:29:00]
a request that goes and gets that from the server and it checks that then you can have
[00:29:06]
your verified email type and you can say how do i get a verified email well the only way
[00:29:12]
to construct a verified email type that it's an opaque type meaning the constructor for
[00:29:18]
verified email is not exposed outside the verified email module so the only way to create
[00:29:23]
one is not from string there's no from string because you can't validate that it is a verified
[00:29:30]
email just by giving it a string there is a you know verified email dot HTTP request
[00:29:37]
that takes a string of an email address it sends it to the server and the server sends
[00:29:43]
back and it and it decodes it decodes into a verified email if it is one so it decodes
[00:29:50]
into a maybe verified email or whatever right yeah so yeah basically the you need a decoder
[00:29:56]
for this to work and if you actually have the htcp request itself including hitting
[00:30:03]
a certain endpoint right then you like if you really trust that I the only way I can
[00:30:12]
get a verified email type is if I am going through this check on the back end so what
[00:30:18]
you mean is that this module should also be the one to create the HTTP request to get
[00:30:25]
the okay yeah sorry I'm exactly I miss that then you can really trust it and again this
[00:30:30]
is like you know if if a malicious client wants to lose those guarantees then it's
[00:30:38]
a client right but this is for bugs right we're talking if we're talking about front
[00:30:42]
end code then we're talking about presentational bugs we're talking about airtight logic that
[00:30:48]
if somebody's not trying to circumvent these things they're not going to see these bugs
[00:30:53]
right yeah I mean I mean to the problem is when you send malicious things to the server
[00:30:59]
anyway and you might as well just do a curl instead of manipulating the front end so right
[00:31:05]
right totally sure people could do it but there are simple way simpler ways around it
[00:31:10]
okay yeah so you do the the request from inside this module and you decode from inside the
[00:31:17]
module so if you want to check something you only have to check whatever is in the module
[00:31:21]
gotcha exactly and then for unwrapping yep right so it's you know it's the same basic
[00:31:30]
concept it's like you know sometimes I think of it like a one stop shop I don't know if
[00:31:35]
you know that phrase but it's let's define it just for those who don't know that I heard
[00:31:41]
of it but I'm not entirely sure it's just sort of like a hokey phrase it's like a cheesy
[00:31:47]
way of saying like come on down to Bill's Boot Barn you can get all your it's your one
[00:31:53]
stop shop for all your rodeo needs you need a lasso and a hat and boots and everything
[00:32:00]
related to going to the rodeo it's your one stop shop right so like that's what you want
[00:32:05]
these opaque types to be if you want to unwrap early and wrap wrap early unwrap late it's
[00:32:13]
just called a supermarket didn't yeah yeah yeah a supermarket is your one stop shop for
[00:32:19]
everything so pretty much although the supermarket you want it to be more of like a boutique
[00:32:24]
shop because the supermarket is responsible for everything but if it's responsible for
[00:32:30]
everything then you're not shielding these different concerns from each other and you're
[00:32:36]
exposing their internals to each other so you want a boutique one stop shop that is
[00:32:43]
a market I also don't want that boutique to be a crook but yeah that too yeah so so basically
[00:32:54]
you know I have a social security number and you have your opaque type how do you make
[00:32:59]
it a one stop shop well how do I get a social security number and how do I use a social
[00:33:05]
security number so so like you were talking about these terminal points where you're forced
[00:33:11]
to unwrap that's where you want to unwrap but then what part of the code is responsible
[00:33:16]
for unwrapping well if you really want to make sure that it's only used in the desired
[00:33:23]
way right so there's like make impossible states impossible but then what about like
[00:33:27]
use data in the desired ways right and that's what the one stop shop is for so when you
[00:33:35]
get to those terminal points sure you could say social security number dot two string
[00:33:40]
but what what that means is that you then have to very carefully ensure that every place
[00:33:47]
in your code base that you call social security number dot two string is done honoring those
[00:33:54]
constraints and responsibly yes exactly exactly right so but if you put that responsibility
[00:34:05]
in the one stop shop you have the social security number module which contains an opaque type
[00:34:10]
it doesn't expose a way to construct one arbitrarily and you say social security number dot view
[00:34:20]
right and so and you can say like social security number dot you could have a masked view if
[00:34:26]
the only way you want to present it in the UI you know maybe you have some input box
[00:34:30]
that if it's focused it's unmasked and if it's not focused it's masked right you could
[00:34:38]
you you could honor that logic in the social security number module right so if you define
[00:34:43]
that that kind of view logic in that module now you only need to look in your one stop
[00:34:48]
shop and that's responsible for it and no other code needs to take on that responsibility
[00:34:53]
yeah but it does tie you to a specific implementation like if you want to have that masked input
[00:35:00]
masked input then it needs to look a specific way and if you want to use it in a different
[00:35:08]
place where it looks slightly different then you're going to need to put that in the same
[00:35:12]
module as well and I can imagine that this could get tricky if you need information from
[00:35:18]
another opaque type as well which uses the same techniques like you need to show both
[00:35:24]
the social security number and some other secret id or number and like both neither
[00:35:34]
of them can show the other so yeah so you necessarily need to put it to give one more
[00:35:40]
control through there or just give control to another module so you definitely have to
[00:35:47]
be careful about where you draw those dividing lines of those boundaries for your opaque
[00:35:52]
types and yeah you you know you you want to decouple these things as much as you can but
[00:35:59]
I think it's a pretty pretty powerful technique especially when you want to be like again
[00:36:04]
for something like a social security number like I I think you have to think about what
[00:36:09]
constraints do you want to to enforce and you know if it's like valid username then
[00:36:17]
you can you can have a from string that's fine right like yeah like how important is
[00:36:21]
it for you to to get the thing right how how costful for you is it to get things wrong
[00:36:29]
social security number pretty important so let's add more security around it let's make
[00:36:35]
it a bit more tricky let's maybe give it a bit more responsibility but for a username
[00:36:40]
for an email address like add a to string add a from string it's fine as long as we
[00:36:46]
can remove most of these errors of related to primitive obsession for instance you should
[00:36:53]
be fine right I mean that's that's about some being pragmatic to some extent right it's
[00:36:59]
like do how much security do we need how likely is it to that you get an error and that it
[00:37:05]
stays there for a while and or that it even gets to production yes I think it's as you
[00:37:10]
say it's pragmatic and it's also just what unique things do you need to do with the type
[00:37:16]
if there's unique logic for how you present a social security number or serializes social
[00:37:23]
security number then in addition to being sensitive and and wanting to be sure that
[00:37:28]
you're carefully managing it and and safely managing it there's also specific logic for
[00:37:34]
how to present it and serialize it right maybe you if you're whatever you know encrypting
[00:37:41]
it before you serialize it to send it to the server or something like that then it's nice
[00:37:47]
to have that logic live alongside it so you can be sure that it's being managed correctly
[00:37:53]
it is a valid username right you have a username type you say username dot from string again
[00:37:59]
the username doesn't tell you where it came from it just tells you that it went through
[00:38:03]
this validation that said this doesn't have any at symbols or exclamation points or it
[00:38:08]
has one at the beginning right depending on the platform right right right yeah whatever
[00:38:13]
checks it has it it's checking those and then how do you display it well just give me the
[00:38:19]
string i just want to put it i want to put it somewhere and i just need the string and
[00:38:23]
that's that that can be fine right so like my point is um it's not only about being pragmatic
[00:38:30]
and saying like how bad would it be if this was used incorrectly how how carefully do
[00:38:36]
i need to manage this to be sure i'm sensitively managing this data but it's also about is
[00:38:42]
there any special logic and if it's like a username it's like no i just want to show
[00:38:47]
this username here there's nothing special about how i show a username i just need the
[00:38:51]
string now because i'd like to put it on the screen you know or i want to get you know
[00:38:56]
i want to compare these two usernames and just give me the strings so i can compare
[00:39:01]
them you know there's no special logic for how i compare two usernames so i don't need
[00:39:05]
to define a username dot is equal function because there's nothing special about that
[00:39:10]
so why would i right so in that case you just don't get any value from defining any distinct
[00:39:17]
functions you just want to get that primitive value when you when you unwrap it and you
[00:39:22]
want to try to unwrap late but it's fine to just then get the primitive and use that so
[00:39:28]
those are the things that i'm thinking about when i'm deciding which which path to go down
[00:39:33]
so there's one area that i've been sort of thinking about in relation to this wrap early
[00:39:38]
unwrap late technique which is deriving this concept of deriving from a source of truth
[00:39:45]
so i think one one important distinction about deriving about wrapping is you don't necessarily
[00:39:53]
need you don't need to parse things into highly structured data in order to wrap yeah you
[00:39:59]
can just add a custom type constructor for username for instance right exactly and it's
[00:40:05]
just wrapping a string and you know so often you want to parse don't validate so you don't
[00:40:10]
want to keep repeatedly checking for an error state over and over but but it can be okay
[00:40:17]
to just wrap something and say hey this thing represents this type of thing and just put
[00:40:22]
a wrapper around a low level type it doesn't so you know sometimes we can like i've been
[00:40:29]
thinking about this with um with managing forms for example like client side form validation
[00:40:35]
i've been sort of questioning this like kind of classic elm way of dealing with forms where
[00:40:42]
we have like a record with first name last name which are strings and we have this like
[00:40:51]
highly structured type you mean to say that not all forms need to contain the first name
[00:40:55]
and the last name well that was just saying i mean more like why do we care what particular
[00:41:04]
fields there could be why don't like why don't we just have key value pairs and say hey these
[00:41:09]
are just at the end of the day the low level representation is just a string and anytime
[00:41:15]
the user types a character i need to validate that to tell them if there are errors and
[00:41:21]
why do we go to the trouble of having this highly structured data so sometimes like prematurely
[00:41:26]
parsing data into something more structured doesn't provide value because we want to sort
[00:41:33]
of have the low level representation keep our options open about how to use it and then
[00:41:38]
derive that for example you can submit form data to a server because you have this nice
[00:41:42]
key value pair right so if you want to unwrap to send something to a server and you need
[00:41:47]
some low level representation you have one if you have form data you have these key value
[00:41:52]
pairs but if you eagerly parse it into this more structured data of a record with with
[00:41:59]
string values then now you've made it structured and you need to sort of make it less structured
[00:42:06]
to serialize it to the server and send it send those as json or whatever where you actually
[00:42:12]
kind of had that already so why go to the trouble of creating that structure prematurely
[00:42:18]
well the nice thing if you do a proper record proper data structure is that it is easy to
[00:42:24]
see what is in the form right like i don't have to look at the view function or the update
[00:42:30]
function probably the view function in your use case to see what fields i'm interested
[00:42:36]
in i see first name okay well i know that i don't care about the first name in this
[00:42:41]
example and then it is somewhat easy to know which fields to to make required and which
[00:42:49]
fields to wait until we have them to send them over to htp to to know that we have that
[00:42:56]
we are done if you put all of that in the view you don't have a nice data structure
[00:43:02]
or some other mechanism that i haven't thought about yet then it can be pretty hard to to
[00:43:09]
know what's happening in your form right i think so what i'm sort of exploring is more
[00:43:15]
i mean there are apis for sort of um decoding forms right that sort of thing and i'm i'm
[00:43:22]
exploring more an idea of taking that low level like i i think of it as the lowest common
[00:43:28]
denominator meaning that it's not highly structured it can be used many different places and those
[00:43:34]
key value pairs are the most basic low level thing that you can derive many things from
[00:43:40]
including running a form decoder or form parser or whatever you want to call it to say hey
[00:43:46]
i expect a first name i expect it to be non empty i expect it to start with an uppercase
[00:43:52]
letter or whatever you know whatever validation logic you have you expect the check in date
[00:43:58]
to be before the checkout date you expect this day to be no longer than this number
[00:44:03]
of days and at least this number of days whatever you have those client side checks to me the
[00:44:07]
source of truth would be that sort of form decoder not and and the form decoder not the
[00:44:15]
values in the form themselves well the source of truth about what fields it expects to be
[00:44:23]
present oh yeah yeah so well you could still have inputs that are not being decoded like
[00:44:33]
you you could mess that up you could have an input for h and then you don't decode that
[00:44:40]
in your decoder but then you could say you know you could have something where you you
[00:44:46]
get the you get some html attributes to put into the into your view for the form inputs
[00:44:55]
from that form decoder right so but but if that's your source of truth then you don't
[00:45:01]
need to decode into this structured intermediary representation like to me there's something
[00:45:08]
distasteful about that idea of having an overly structured intermediary representation like
[00:45:13]
i want to have if i can immediately parse something into a well structured representation
[00:45:22]
right away and i don't need to do anything else with it that's great or otherwise if
[00:45:27]
something you know with with form data it's something that's changing on every keystroke
[00:45:33]
and it's going it can go from invalid to valid to invalid to valid then having a highly structured
[00:45:41]
intermediary representation doesn't give me anything and in fact it like adds all this
[00:45:46]
wiring if you want to have logic that says you know i want to track the state for each
[00:45:52]
one you need to wire up messages for each of those you need to make sure you're wiring
[00:45:55]
them up correctly you need to manage this form state carefully that's saying whether
[00:46:01]
a field has been blurred or focused before and so i like the idea of being able to have
[00:46:07]
like a low level representation where you have logic that can deal with those basic
[00:46:13]
building blocks rather than like having it coupled to this specific intermediary representation
[00:46:20]
that that feels off to me yeah there's also something that is very disappointing with
[00:46:25]
the intermediate representation is that it's still quite low level code or low level low
[00:46:32]
level data right because for instance if you if you request the age and age should be a
[00:46:38]
number and you have an input for that which where you can type in whatever number maybe
[00:46:45]
even a string but there is some yes there there is some input fields that only accept
[00:46:51]
the numbers but they still accept the letter e i think for exponentiation right if someone
[00:46:58]
removes the last character then you end up with a empty string and that is not decodable
[00:47:04]
to a number and that usually creates some problems like the cursor moving stuff like
[00:47:10]
that so what we generally recommend people to do is to store the string anyway in the
[00:47:17]
model and then to whenever you want to do something with it save it validate it send
[00:47:23]
it over to the hdp request then you parse it then you validate it you decode it into
[00:47:30]
a proper age which is an integer and if you fail then you don't do any of those things
[00:47:35]
so yeah even if you have a nice record a nice model you still need to store lower level
[00:47:44]
data than you would like to and that has been that is annoying yeah yeah and you you have
[00:47:49]
to you know have a lot of logic that's highly coupled to your implementation and as your
[00:47:56]
implementation changes as you add new fields and change fields you need to keep writing
[00:48:01]
code to manage those new pieces which just doesn't feel right it's like you should just
[00:48:06]
be able to write a form decoder and have the the logic for maintaining that low level state
[00:48:14]
because it is low level state not be coupled to your specific you know implementation and
[00:48:21]
so so yeah so i think that's i'm exploring some of those things for for an api i'm working
[00:48:28]
on in on pages three but it it's made me think a little bit about you know this you know
[00:48:33]
it's somewhat related but it's this concept of deriving from from a source of truth and
[00:48:38]
what should the representation of the source of truth be but ideally you wrap that at least
[00:48:44]
to to have it represent the the types of interactions you can have with it and again this this like
[00:48:50]
wrapping it's not that you can never use like a low level representation like a dictionary
[00:48:56]
or something like that but you should think about what kinds like what do these types
[00:49:03]
allow you to do with with them and if they allow you to do undesirable things with them
[00:49:09]
then you should wrap it and prevent those and define the interface you want for it so
[00:49:15]
if you um if you shouldn't be able to insert something into it except through a form field
[00:49:22]
getting changed then if you have a dictionary you're inviting that type of interaction so
[00:49:28]
you don't get to control the interface of how you want something to be used so you know
[00:49:32]
the the wrapping comes down to um defining what a type represents what validations does
[00:49:39]
it represent does it represent anything about where a place came from and does it what what
[00:49:45]
constraints do you want to capture for how the thing can be used where the thing can
[00:49:50]
be used and how to present it and how to use it well maybe we should uh maybe we should
[00:49:57]
wrap early on this one or are we unwrapping late no no that doesn't no that doesn't make
[00:50:06]
any sense so do you have any tips on how to get started with wrap early and unwrap late
[00:50:14]
other than the ones that we've mentioned so far and using opaque types and avoiding primitive
[00:50:19]
obsession uh yeah definitely uh if you if you haven't give those episodes a listen because
[00:50:26]
there's a lot of relevant stuff in in our intro to opaque types episode and in i mean
[00:50:32]
honestly it's required listening we should if if we could have a check at the gate before
[00:50:39]
somebody listens to to elm radio they must listen to our intro to opaque types then we
[00:50:45]
would but we don't know how to do that yet we're working on it yeah definitely give those
[00:50:51]
listen i'll drop some links to my blog posts about entry and exit gatekeepers and yeah
[00:50:58]
i also i've mentioned it on a previous episode but i have a there's a youtube video where
[00:51:03]
i presented some ideas walking through some of this stuff about the social security number
[00:51:08]
example so i'll drop a link to that and yeah otherwise you know again i mean experiment
[00:51:14]
with it and pay attention to what are the constraints you know we talked about like
[00:51:19]
the pragmatic concerns and also just like saying what what constraints do you want it's
[00:51:25]
at the end of the day you've got to think about the problems you're solving there's
[00:51:29]
no no way around it there's no one size fits all solution because it completely depends
[00:51:35]
on the constraints you're trying to model so really think about your constraints and
[00:51:40]
think about how your types support those constraints or or not and and think about are you are
[00:51:48]
you managing those constraints in one centralized place in an opaque types module or are you
[00:51:55]
managing those constraints everywhere you use the type all around your code base to
[00:52:00]
me that's the that's the big question i'm asking when i'm um how how much can i trust
[00:52:07]
a type by using the interface it exposes for me to use it versus how much do i have to
[00:52:13]
carefully manage those constraints outside of that so that it's a question of like where
[00:52:18]
does the responsibility belong and it it just kind of feels off if i'm really worried about
[00:52:23]
hmm i want to make sure i don't mess up how i'm using the social security number and i'm
[00:52:28]
not working in the social security number module that feels off that feels like wait
[00:52:33]
that shouldn't be my responsibility i should be able to trust this module to use it safely
[00:52:38]
so i should delegate some more of that responsibility there and constrain what i can do with uh
[00:52:45]
you know how i can create it should be more constrained and how i can use it from within
[00:52:50]
that module should be more constrained instead of just exposing two string well you're in
[00:52:56]
until next time until next time