Wrap Early, Unwrap Late

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


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