Parse, Don't Validate (2019)
lexi-lambda.github.io
lexi-lambda.github.io
For those who don't necessarily program in statically typed functional languages:
The idea transcends paradigms.
You'll find very similar notions in 80's/90's OO literature, for example in Design by Contract. I'm sure one can dig deeper and find papers, discussions and specifications that go further back.
I think TypeScript is often written in such a way where you refine the types at runtime. I assume Design by Contract has influenced Clojure's spec (Clojure is a dynamic language).
Fundamentally this is about assumptions and guarantees (or requiring and providing). Once an assumption is checked and guarantees can be made, then other parts of the program don't need to check overlapping assumptions again.
In fact I think one of the most confusing things when you read code is seeing already guaranteed properties being checked again somewhere else. It makes code harder to reason about and improve.
Because at some point those "already guaranteed properties" might "disappear", to be more exact, the process/procedure that implements and runs them might not do its thing anymore for one reason or another.
When that happens, because statistically speaking it will happen, then all the other processes/scripts/pieces of code depending on that "original" validating process would be in a very rough place.
Of course, this stops being true when you start hacking around the checks, side-stepping the parsers entirely (say, type-casting String to Username without any check). There's nothing a language can do to stop you when you really want to do this[0] - but then, you're an adult; if you see restrictions and safety interlocks, and put effort to hack around them, then any problem is really on you.
This gets more difficult in dynamically typed languages, as you have to rely more on naming conventions and programmers not being idiots, but even without a typechecker, it will be rather obvious when you're doing something that could break the chain of guarantees.
--
[0] - Some can try; I've seen a Haskell paper about this idea that does very complex type magic to try and truly ensure that only the parsing function can actually construct the result type. I tried reproducing that in C++ once, but C++ just can't give such guarantees.
Rust does this with standard types even. "Parse, don't validate" is the answer to the question: why does Rust have so many string types? Of course you can hack around this using `unsafe` but, as you say, in that case you have to put visible effort into deliberately subverting the type system.
For example take this library: https://github.com/google/uuid
It represents UUIDs as `type UUID [16]byte`.
It would be trivial to circumvent a constructor: `var uuid UUID`. Go values are zero initialized.
(Aside: You might actually have a reason to do this, but often you would rather use https://pkg.go.dev/github.com/google/uuid#NullUUID).
But it's obvious that you would use one of the constructors in the library to get an actual valid UUID.
Sure, the farther away from your control and trust circle, the more you're inclined to check assumptions that are already guaranteed. It's a valid reason to do this as you explained, but I think the tradeoff has to be considered:
Generally speaking, checking assumptions that are already guaranteed is a _bug_. It violates the DRY principle[0], and will break your program, when those guarantees relax or the assumptions become tighter at one place or another, because they diverge.
And again, it can be confusing for maintainers and makes it harder to reason about a program and make sensible changes. The anxiety that drives the checks will leak right into the reader who might be wary of breaking Chesterton's Fence. Now you have someone testing everything in order to figure out if there are code paths that only hit one or the other of the checking code and stuff like that.
Needless to say it can also make performance worse, because you're doing more work than needed, especially if the checks require fetching data from disk or the network etc. This type of performance degradation is quite common.
[0] The real/actual one, not the one where people factor out superficially repetitive code.
But you have to use it. E.g. by having a Class UncheckedEmail, a Class ValidEmail and a Class VerifiedEmail and ensuring that the conversion from one to the other has to involve your email-verification process.
That way you never have to guess whether the email adress is unchecked, valid or verified and there is no need for "is_email_verified" booleans that you may or may not forget to update/check. If you use the wrong thing in the wrong place your type checker yells at you, while you can focus on actually important stuff.
'Int' is fine. We don't need ActualInt and IPromiseItsReallyAnInt.
I assumed there is a typical registration process. Future users of your application input a text in a field that is meant to be a reachable email address. So the first step would be to filter out garbage or typoed text that your mail-sending function would not be able to handle. And because you don't know how long people need to click on a link in their mail you need to store and work with that validated-but-unverified email address till they do. And for this having your own type that cannot be mixed up with the verified email addresses can make sense. Depending on how complex your application is.
These so called aggregates can only be created from a factory to avoid bad initialization. This way you never need to make ad hoc validations to be “ really really reallysure”.
Somewhat less bad would be IEEE 754 floating point numbers - where your floating-point "numbers" can include both +0 and -0, sub-normal numbers, infinities, NaNs, and other miseries.
Then program using very poor analogies, like Int.
String->(valid)Email is 'Parse', (invalid)Email->ValidEmail is 'Validate'.
> That way you never have to guess whether the email adress is unchecked
Now you just get to guess whether, for every class, there is actually a ValidClass that you were supposed to be using, instead of Class.
authenticate(User user);
^ This is a bug. We just let a malicious User object in because we invested extra time, effort and sloc into making both a User and a ValidUser class, disregarding the first "simple idea" from the article:>> 1. Use a data structure that makes illegal states unrepresentable
Or as I wrote:
> only make classes for valid objects
The type system stops this. You can't pass, e.g. a User to authenticate because it only accepts a ValidUser.
> only make classes for valid objects
Using primitives (String, Int, etc.) for unvalidated input, and custom classes for validated input is fine in many cases. However, sometimes you need to represent data that is in the process of being validated (e.g. when validation takes time, like waiting for a user to validate their email address) and then these intermediate classes arrive.
Be careful about how the email is implicitly verified though, and how different login methods and assumptions interact when for example migrating users from one system or platform to another.
https://krebsonsecurity.com/2024/07/researchers-weak-securit...
> analysis released by security experts at Metamask and Paradigm finds the most likely explanation for what happened is that Squarespace assumed all users migrating from Google Domains would select the social login options — such “Continue with Google” or “Continue with Apple” — as opposed to the “Continue with email” choice
> Squarespace never accounted for the possibility that a threat actor might sign up for an account using an email associated with a recently-migrated domain before the legitimate email holder created the account themselves
> since there’s no password on the account, it just shoots them to the ‘create password for your new account’ flow. And since the account is half-initialized on the backend, they now have access to the domain in question
This kind of mistake makes me think that it’s better to be super explicit about the state of everything when it comes to accounts.
So even if it may seem excessive to have VerifiedEmail as a type instead of marking the account as verified or not. I will prefer being explicit.
And in medium to big size systems with multiple pieces of user account data that require separately keeping track of verification state it will be necessary anyway. For example even something as simple as having one email and one phone number associated with a user. Or beyond that one user having multiple email addresses or multiple phone numbers etc.
My point was specifically about complexity. Using a string for all kind of emails of course works, tying guarantees to other things like VerifiedUser works as well. These implicit guarantees just fail to safe your ass once things get more complex and there is an edge case you didn't think of.
In your own example, if you happen to use e-mail verification as proxy for user verification, then the very code that creates VerifiedUser instances would want to have VerifiedEmail as input!
Getting hung up on the details of which type you use is missing the overall point.
Your state machine analogy is a good one, because the conversion between different types is a bit like the transitions between states: you have to make explicit how exactly they are meant to happen (or whether they are actually possible).
This is a good thing, having less degrees of freedom may seem like it makes it harder to code, but in fact it allows you to reason better about what the system is doing at any given point in your code.
This is good, because you as the programmer can rely on the fact that wherever you see a string of type VerifiedMail, that it is indeed a string containing a verified email adress, you don't even need to check, because you know the conversion between the different types had be done explicitly.
You can of course extend the whole thing and have a OnboardUser with a ValidatedMail and only convert the OnboardUser into an actual User once there is a VerifiedMail etc.
You get the idea. Whenever you find yourself wondering if a variable is actually holding the expected information, it is a good idea to leverage the type system to replace wondering with knowing.
But you are right: unchecked email could as well just be a string.
For example "Goto considered harmful". I remember working with a very good programmer who'd used a "goto", and a much less senior one[1] rejected their PR by linking to the article.
[1] I'm ashamed to say it was me, a long time ago.
It gives the person making the change the opportunity to give context for their decision - and either change or spell out their reasoning for it, potentially giving me an opportunity to learn from them.
Another good benefit is that it doesn't "attack" the PR author in the "non-violent communication" way
if(!Whatever.TryParse<Thingy>(input, out var output)) output = some-sane-default;
or: if(!Whatever.TryParse<Thingy>(input, out var output)) throw new ApplicationException($"Not a valid Thingy: {input}");
Protip: don't do the latter in your kernel-mode driver.Explicit is always better than implicit defaults that get used instead when you give it a wrong value that you think is correct.
What you should do is throw your hands up early, fail to parse, and have a very clearly defined process and protocol to handle files that couldn't be loaded. It'll force you to ask yourself very difficult questions that aren't covered by either of the two options you posted.
The real failure in the recent Crowdstrike kernel-mode driver failing to parse some def/config file is that the dev/product owner/BA didn't ask "what happens if we try load a file that's invalid?"
Why only "good-ish"? And how does it relate to the year the article was published? Surely you are implying that the advice in the article would be more authoritative if it were published earlier than 2019, right?
Explicit > Implicit
E.g. you don't want to refuse to open a document just because ten pages in there is a footnote that is somehow malformed.
Depending on the possible consequences, GIGO is much better for usability. Ideally you'd warn the user that something unexpected has occured and he should manually check the result but whenever you are handling external data where malformed input cannot be avoided then erroring early will only make your software less useful to the user who more often than not cannot do anything about the error but can manually correct the fallback if you got it wrong.
I absolutely hate that. IMO you should handle the error of an invalid input outside of the function to parse. F# makes that easy.
type Whatever =
static member create input =
match input with
| ValidWhatever x -> Some x
| _ -> None
match Whatever.create input with
| Some x -> //process the parsed data
| None -> //handle it not being parsed well
Or you could also use Option.map/Option.bind to build a pipeline to handle chained operations in an ergonomic manner.With this, you can only instantiate any instances through the create method with parses the input.
Altough, you probably want to use a result rather than option, but I digress.
I can't think of many (probably any) situations where I'd want to find that.
If _no_ input is provided (i.e., the parameter is optional), sure, using a sane default makes sense.
If _invalid_ input is provided, for the love of god please don't pretend like nothing's wrong.
If someone walks into a florist and asks for a coffee, the correct answer is not for them to be handed a rose. They're going to cut their mouth all up when they try and drink it.
Your method/module/program does not have an output defined for that set of inputs. Make that obvious rather than just doing wrong or non-obvious things in a way that quickly makes your program almost impossible to reason about. Do yourself a favour and clearly raise the issue and leave yourself a stack trace pointing directly to the issue instead of setting yourself up for the vague bug about incorrect behaviour when someone catches this in a few months.
Similarly when converting a file you often should not abort the operation just because there is some minor detail in the original that you can't parse. The user is generally not in a position where they can do anything about such an error but they can often fix up the partially correct result.
Of course you shouldn't do the fallback silently and at least notify the user that there was a problem.
>Parse, don’t validate.
For me the slogan is rather "always validate only in the single constructor" (or constructor function, doesn't matter). That way, you cannot have invalid objects at all, and there's always a single source of truth. If you want to modify the object, implement it via constructing a new state by calling the same constructor again.
The point is that validation alone is then lost as information later on.
E.g. validating an int to be positive has limited benefits if you don't parse it to be a positive int, because there's no such information at the type level later on, same could apply to a non empty array/list where following consumers would then need to check again if the list is really non empty.
This kind of information cannot always be encoded in objects or constructors.
If I were were to teach a class about programming in the medium (as opposed to in the small or in the large), I think I'd assign my students an essay comparing and contrasting these suggestions. Each has something to teach us, and maybe they're not as contradictory as it may seem at first.
It beats me why people didn't want to write parsers, though. Writing parsers is not that hard, and is quite fun.
It's the same kind of ground as avoiding primitive obsession.
> "“required” keyword in Protocol Buffers turned out to be a horrible mistake"
https://capnproto.org/faq.html#how-do-i-make-a-field-require...
Having both flexible, unvalidated parsing and validated parsing functions would probably be best IMHO.
1. A field being "required", is not a property of the field itself, but of the construct holding the field. JSON-Schema does this correctly, by letting you define an array of required fields on the schema of an object instead of it being a property of a specific field.
2. Consumers, not producers should decide what their assumptions are. Producers should decide what their guarantees are.
3. Everyone, including the parts in the middle (here it's a message bus) should only state assumptions that they actually need in order to function. Use different schemas for different assumptions (which is easier to do if the "required" assumption is a property of a construct and not a field).
The example in the article you mentioned illustrates nicely how these three principles are broken by "required" (or maybe how Protocol Buffers are used in general).
Issue with schema definition languages is that only one set classes is generated. Forcing consumers to create large amounts of mapping logic to transform input to objects with required assumptions.
I like the idea of an array of required fields on the schema of an object. Using arrays of required fields, could be used to generate different set of classes. That would make writing near-duplicate classes of input objects and mapping logic unnecessary.
In this case you could make a wrapper function that accepts the raw binary data, passes it to Cap'n'Proto, validates the output (this field is actually required etc) and then returns it
More than merely catching compiler errors, this also makes writing code faster by offering better auto-completion for new code.
For all projects of some. Size I would advice people to use Zod or the like (unless there are special circumstances such that external deps can not be used).
https://github.com/effect-ts/effect/tree/main/packages/schem...
That's actually not really correct. Or rather, it is technically correct but it will confuse the readers who work in languages like Java.
While void in languages like Java means that the result of the function cannot be used or has no meaning, it is NOT equivalent to types like the bottom type of Haskell. Because that would mean that the function can never return.
Rather, void is similar to the "unit type" (https://en.wikipedia.org/wiki/Unit_type) which does have a value. It's like an empty tuple. It contains no information other then "the function call has finished". (and of course in languages with exceptions, this means that no exception was thrown)
Otherwise, I like the article. More people should read and understand this way of thinking.
The key idea seems to be that the border between the periphery/plumbing/deserialization code and the actual business logic should be as strict and direct and isolated as possible. Only pass objects/data/payloads to the business logic that have been fully ingested into the data model of the business logic. And keep the ingestion in one place.
From this perspective, the section about "shotgun parsing" might give some people the wrong idea and derail some discussions: If it's an actual part of the business requirements that branching and validations need to happen (branching for example over the existence of an optional value), a superficial reading of the article might lead someone to incorrectly identify this as "shotgun parsing".
type NonEmpty<T> = [T, ...T[]]
const head = <T>(list: NonEmpty<T>) => list[0]
function getConfigurationDirectories(): NonEmpty<string> {
const configDirsString = process.env["CONFIG_DIRS"]
const [firstDir, ...restDirs] = configDirsString.split(',')
if (firstDir === undefined) throw Error("CONFIG_DIRS cannot be empty");
return [firstDir, ...restDirs];
}
function main() {
const configDirs = getConfigurationDirectories();
initializeCache(head(configDirs))
}It seems like this approach will often bottom out in smart constructors since type systems either are limited or make you work too hard to prove relatively simple thing.
(from my own notes https://x.com/swyx/status/1548380295765733378)
Not in unit tests but when the app is running: you take the data in, you parse it, you re-encode/re-serialize it/re-whatever it. If it's not matching the data that came in, the data that came in is rejected.
And that should just be one of the steps taken to verify that the data looks legit.
Booleans are harmful for other reasons, sometimes related to "parse, don't validate". Write a discriminated union that makes the true/false distinction meaningful, if you really do have a two-valued data type ("--dry-run" is `| Dry | Wet`, not a bool; this mistake is called "boolean blindness"). Write a type that actually contains the data you want, if the boolean is supposed to indicate the validity of some other data (`option<string>`, not `string * bool` where `something, false` implicitly means the `something` is meaningless).
You can use pointers in some cases, but then everything becomes a full-blown pointer for the lack of an Option<T> type, and it still doesn't fully solve the problem.
When parsing "enums" in go, which are really just a type alias and some loose constants, again it's not possible to prevent zero values sneaking in with the stdlib json package. E.g. you get a value of the correct type, but it's not one of your consts, but equivalent to a new value `YourType("")`.
Thus, a lot of validation is necessary, between parsing and using values.
comprehensive test coverage meets the same goal, and TDD looks a lot like "type-driven design", only easier to read and maintain.
I never understood why the default for [a] means that the list could be empty. If ...
foo: [a] -> a
... foo is supposed to get a list of a's, it should get a list of a's, with at least one a. If the list can be empty, then explicitly annotate it so: foo: [a*] -> a
One way or the other one has to deal with the empty list explicitly (in the signature). If you allow empty lists, it will have to return a 'Maybe a'. It seems to me that it just makes processing the result easier in the common case if the input were to be constrained.```ocaml
type 'a nonempty = Single of 'a | Cons of 'a * 'a nonempty
```
This would be the type of lists that contain one or more elements of the type parameter.
I think it's just convention that typically, when we talk about lists, we are interested in the empty case as well. Finding "all X that satisfy P in Y", as a general computational problem, is _very_ common (consider: filtering a list, querying for a predicate in a collection, finding sequences of moves in a search space), and generally could result in an empty list as a possible output.
In a non-practical sense, if you want the type theory, another reason is you can think of `[a]` as the free monoid on the collection of `a`. In other words, strings of elements of `a`, joined via concatenation. This monoid requires a unit, which is the empty list.
Probably makes more sense when you come from imperative programming background, where List<Int> is a piece of mutable state that you can construct and then fill, in two explicitly separate steps (and then possibly empty it again in yet another step). Then again, I believe even mathematicians are fine with ideas of an empty set, or of a one-element set being distinct from the element itself.
Especially mathematicians. Distinguishing stuff and structure is a common theme throughout mathematics.
The notation [a] could just as easily have meant a non-empty list
That isn't to say you _have_ to write it this way for new things and there are packages like https://hackage.haskell.org/package/safe which provide non-partial/safe versions of the various unsafe base functions (like head, maximum and friends).
The base package in Haskell also includes nonempty which is probably what you want in a lot of cases, anyway.
Not saying that this advice isn't solid, just thought it's funny given the news of this week.
If you have exposed APIs, you should prevent malicious payloads and what happens when the parser can be broken through invalid data, causing also Out of Memory exceptions?
It might work only if you have some safe guardrails around the APIs, but just exposing naked endpoints, without a minimum of checks or a Web Application Firewall, this isn't a real good advice