spotifyovercastrssapple-podcasts

Wrap Early, Unwrap Late

We dive into preventing bugs and making your code more maintainable by wrapping early and unwrapping late.
June 20, 2022
#59

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