8 ms·
Maybe I'm missing something and I'm glad this idea resonates, but it feels like sometime after Java got popular and dynamic languages got a lot of mindshare, a
by seanwilson 8mo ago
Maybe I'm missing something and I'm glad this idea resonates, but it feels like sometime after Java got popular and dynamic languages got a lot of mindshare, a large chunk of the collective programming community forgot why strong static type checking was invented and are now having to rediscover this.
In most strong statically typed languages, you wouldn't often pass strings and generic dictionaries around. You'd naturally gravitate towards parsing/transforming raw data into typed data structures that have guaranteed properties instead to avoid writing defensive code everywhere e.g. a Date object that would throw an exception in the constructor if the string given didn't validate as a date (Edit: Changed this from email because email validation is a can of worms as an example). So there, "parse, don't validate" is the norm and not a tip/idea that would need to gain traction.
- bcrosby95 8mo agoIn my experience that's pretty rare. Most people pass around string phone numbers instead of a phonenumber class. Java makes it a pain though, so most code ends up primitive obsessed. Other languages make it easier, but unless the language and company has a strong culture around this, they still usually end up primitive obsessed.
- vips7L 8mo agorecord PhoneNumber(String value) {} Huge pain.
- kleiba 8mo agoWhat have you gained?
- jalk 8mo agoAn explicit type
- dylan604 8mo agoObviously the pseudo code leaves to the imagination, but what benefits does this give you? Are you checking that it is 10-digits? Are you allowing for + symbols for the international codes?
- munk-a 8mo agoThat's going to be up to the business building the logic. Ideally those assumptions are clearly encoded in an easily readable manner but at the very least they should be captured somewhere code adjacent (even if it's just a comment and the block of logic to enforce those restraints).
- bjghknggkk 8mo agoHow to make a crap system that users will hate: Let some architecture astronaut decide what characters should be valid or not.
- bjghknggkk 8mo agoAnd parentheses. And spaces (that may, or may not, be trimmed). And all kind of unicode equivalent characters, that might have to be canonicalized. Why not treat it as a byte buffer anyway.
- JambalayaJimbo 8mo agoIf you are not checking that the phone number is 10 digits (or whatever the rules are for the phone number for your use case), it is absolutely pointless. But why would you not?
- jghn 8mo agoI would argue it's the other way around. If I take a string I believe to be a phone number and wrap it in a `PhoneNumber` type, and then later I try to pass it in as the wrong argument to a function like say I get order of name & phone number reversed, it'll complain. Whereas if both name & phone number are strings, it won't complain. That's what I see as the primary value to this sort of typing. Enforcing the invariants is a separate matter.
- munk-a 8mo agoWithout any other context? Nothing - it's just a type alias... But the context this type of an alias should exist in is one where a string isn't turned into a PhoneNumber until you've validated it. All the functions taking a string that might end up being a PhoneNumber need to be highly defensive - but all the functions taking a PhoneNumber can lean on the assumptions that go into that type. It's nice to have tight control over the string -> PhoneNumber parsing that guarantees all those assumptions are checked. Ideally that'd be done through domain based type restrictions, but it might just be code - either way, if you're diligent, you can stop being defensive in downstream functions.
- seanwilson 8mo ago> All the functions taking a string that might end up being a PhoneNumber need to be highly defensive Yeah, I can't relate at all with not using a type for this after having to write gross defensive code a couple of times e.g. if it's not a phone number you've got to return undefined or throw an exception? The typed approach is shorter, cleaner, self-documenting, reduces bugs and makes refactoring easier.
- thfuran 8mo ago>But the context this type of an alias should exist in is one where a string isn't turned into a PhoneNumber until you've validated it. Even if you don't do any validation as part of the construction (and yeah, having a separate type for validated vs unvalidated is extremely helpful), universally using type aliases like that pretty much entirely prevents the class of bugs from accidentally passing a string/int typed value into a variable of the wrong stringy/inty type, e.g. mixing up different categories of id or name or whatever.
- barmic12 8mo agoone issue is it’s not a type alias but a type encapsulation. This have a cost at runtime, it’s not like in some functionnals languages a non cost abstraction.
- LelouBil 8mo agoIn languages like kotlin and rust you can have a type encapsulation like this that does not exist at runtime
- waynesonfire 8mo agoWhat did you lose?
- esafak 8mo agoValidation, readability, and prevention of accidentally passing in the wrong string (e.g., by misordering two strings arguments in a function).
- kleiba 8mo agoI don't see any validation here.
- deleted 8mo ago[deleted]
- jonathanlydall 8mo agoI’m very much a proponent of statically typed languages and primarily work in C#. We tried “typed” strings like this on a project once for business identifiers. Overall it worked in making sure that the wrong type of ID couldn’t accidentally be used in the wrong place, but the general consensus after moving on from the project was that the “juice was not worth the squeeze”. I don’t know if other languages make it easier, but in c# it felt like the language was mostly working against you. For example data needs to come in and out over an API and is in string form when it does, meaning you have to do manual conversions all the time. In c# I use named arguments most of the time, making it much harder to accidentally pass the wrong string into a method or constructor’s parameter.
- Akronymus 8mo agoIn f# you can use a single case discriminated union to get that behaviour fairly cheaply, and ergonomically. https://fsharpforfunandprofit.com/posts/designing-with-types-single-case-dus/ https://fsharpforfunandprofit.com/posts/designing-with-types...
- yakshaving_jgt 8mo agoIt's a design choice more than anything. Haskell's type safety is opt-in — the programmer has to actually choose to properly leverage the type system and design their program this way.
- pjerem 8mo ago> In most strong statically typed languages, you wouldn't often pass strings and generic dictionaries around. In 99% of the projects I worked on my professional life, anything that is coming from an human input is manipulated as a string and most of the time, it stays like this in all of the application layers (with more or less checks in the path). On your precise exemple, I can even say that I never saw something like an "Email object".
- Boxxed 8mo agoWell that's terrifying
- tracker1 8mo agoWhat's funny, is this is exactly one of the reasons I happen to like JavaScript... at its' core, the type coercion and falsy boolean rules work really well (imo) for ETL type work, where you're dealing with potentially untrusted data. How many times have you had to import a CSV with a bad record/row? It seems to happen all the time, why, because people use and manually manipulate data in spreadsheets. In the end, it's a big part of why I tend to reach for JS/TS first (Deno) for most scripts that are even a little complex to attempt in bash.
- jghn 8mo agoI've seen a mix between stringly typed apps and strongly typed apps. The strongly typed apps had an upfront cost but were much better to work with in the long run. Define types for things like names, email address, age, and the like. Convert the strings to the appropriate type on ingest, and then inside your system only use the correct types.
- deleted 8mo ago[deleted]
- rileymichael 8mo agothis is likely an ecosystem sort of thing. if your language gives you the tools to do so at no cost (memory/performance) then folks will naturally utilize those features and it will eventually become idiomatic code. kotlin value classes are exactly this and they are everywhere: https://kotlinlang.org/docs/inline-classes.html https://kotlinlang.org/docs/inline-classes.html
- wat10000 8mo agoI'm not sure, maybe a little bit. My own journey started with BASIC and then C-like languages in the 80s, dabbling in other languages along the way, doing some Python, and then transitioning to more statically typed modern languages in the past 10 years or so. C-like languages have this a little bit, in that you'll probably make a struct/class from whatever you're looking at and pass it around rather than a dictionary. But dates are probably just stored as untyped numbers with an implicit meaning, and optionals are a foreign concept (although implicit in pointers). Now, I know that this stuff has been around for decades, but it wasn't something I'd actually use until relatively recently. I suspect that's true of a lot of other people too. It's not that we forgot why strong static type checking was invented, it's that we never really knew, or just didn't have a language we could work in that had it.
- conartist6 8mo agoI think you're quite right that the idea of "parse don't validate" is (or can be) quite closely tied to OO-style programming. Essentially the article says that each data type should have a single location in code where it is constructed, which is a very class-based way of thinking. If your Java class only has a constructor and getters, then you're already home free. Also for the method to be efficient you need to be able to know where an object was constructed. Fortunately class instances already track this information.
- Archelaos 8mo agoStrong static type checking is helpful when implementing the methodology described in this article, but it is besides its focus. You still need to use the most restrictive type. For example, uint, instead of int, when you want to exclude negative values; a non-empty list type, if your list should not be empty; etc. When the type is more complex, specific contraints should be used. For a real live example: I designed a type for the occupation of a hotel booking application. The number of occupants of a room must be positiv and a child must be accompanied by at least one adult. My type Occupants has a constructor Occupants(int adults, int children) that varifies that condition on construction (and also some maximum values).
- imtringued 8mo agoUsing uint to exclude negative values is one of the most common mistakes, because underflow wrapping is the default instead of saturation. You subtract a big number from a small number and your number suddenly becomes extremely large. This is far worse than e.g. someone having traveled a negative distance.
- Archelaos 8mo agoIn C# I use the 'checked' keyword in this or similar cases, when it might be relevant: c = checked(a - b); Note that this does not violate the "Parse, Don't Validate" rule. This rule does not prevent you from doing stupid things with a "parsed" type. In other cases, I use its cousin unchecked on int values, when an overflow is okay, such as in calculating an int hash code.
- lelanthran 8mo ago> The number of occupants of a room must be positiv and a child must be accompanied by at least one adult. My type Occupants has a constructor Occupants(int adults, int children) that varifies that condition on construction (and also some maximum values). Or, you could do what I did when faced with a similar problem - I put in a PostgreSQL constraint. Now, no matter which application, now or in the future, attempts to store this invalid combination, it will fail to store it. Doing it in code is just asking for future errors when some other application inserts records into the same DB. Business constraints should go into the database.
- css_apologist 8mo agoThis is an idea that is not ON or OFF You can get ever so gradually stricter with your types which means that the operations you perform on on a narrow type is even more solid It is also 100% possible to do in dynamic languages, it's a cultural thing
- jackpirate 8mo ago> Edit: Changed this from email because email validation is a can of worms as an example Email honestly seems much more straightforward than dates... Sweden had a Feb 30 in 1712, and there's all sorts of date ranges that never existed in most countries (e.g. the American colonies skipped September 3-13 in 1752).
- flqn 8mo agoDates are unfortunate in that you can only really parse them reliably with a TZDB.
- legulere 8mo agoIt’s a ISO-standard to use Gregorian dates even for dates predating its invention. If you need to support anything else (I never had to in my Eurocentric work so far), you’ll need to model calendars, similar to how temporal did for JavaScript: https://tc39.es/proposal-temporal/docs/calendars.html https://tc39.es/proposal-temporal/docs/calendars.html
- brooke2k 8mo agothis is very much a nitpick, but I wouldn't call throwing an exception in the constructor a good use of static typing. sure, it's using a separate type, but the guarantees are enforced at runtime
- zanecodes 8mo agoGiven that the compiler can't enforce that users only enter valid data at compile time, the next best thing is enforcing that when they do enter invalid data, the program won't produce an `Email` object from it, and thus all `Email` objects and their contents can be assumed to be valid.
- mh2266 8mo agoThis is all pretty language-specific and I think people may end up talking past each other. Like, my preferred alternative is not "return an invalid Email object" but "return a sum type representing either an Email or an Error", because I like languages with sum types and pattern matching and all the cultural aspects those tend to imply. But if you are writing Python or Java, that might look like "throw an exception in the constructor". And that is still better than "return an Email that isn't actually an email".
- zanecodes 8mo agoAh yeah, I guess I assumed by the use of the term "contructor" that GP meant a language like Python or Java, and in some cases it can difficult to prevent misuse by making an unsafe constructor private and only providing a public safe contructor that returns a sum type. I definitely agree returning a sum type is ideal.
- imtringued 8mo agoI agree and for several reasons. If you have onerous validation on the constructor, you will run into extremely obvious problems during testing. You just want a jungle, but you also need the ape and the banana.
- noelwelsh 8mo agoIn 2 out of 3 problematic bugs I've had in the last two years or so were in statically typed languages where previous developers didn't use the type system effectively. One bug was in a system that had an Email type but didn't actually enforce the invariants of emails. The one that caused the problem was it didn't enforce case insensitive comparisons. Trivial to fix, but it was encased in layers of stuff that made tracking it down difficult. The other was a home grown ORM that used the same optional / maybe type to represent both "leave this column as the default" and "set this column to null". It should be obvious how this could go wrong. Easy to fix but it fucked up some production data. Both of these are failures to apply "parse, don't validate". The form didn't enforce the invariants it had supposedly parsed the data into. The latter didn't differentiate two different parsing.
- rzwitserloot 8mo agothat's a bit of a hairy situation. You're doing it wrong. Or not really, but.. complicated. As per [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321.html https://www.rfc-editor.org/rfc/rfc5321.html): > the local-part MUST be interpreted and assigned semantics only by the host specified in the domain part of the address. You're not allowed to do that. The email address `foo@bar.com` is identical to `foo@BAR.com`, but not necessarily identical to `FOO@bar.com`. If we're going to talk about 'commonly applied normalisations at most email providers', where do you draw that line? Should `foo+whatever@bar.com` be considered equal to `foo@bar.com`? That souds weird, except - that is exactly how gmail works, a couple of other mail providers have taken up that particular torch, and if your aim is to uniquely identify a 'recipient', you can hardcode that `a@gmail.com` and `a+whatever@gmail.com` definitely, guaranteed, end up at the same mailbox. In practice, yes, users _expect_ that email addresses are case insensitive. Not just users, even - various intermediate systems apply the same incorrect logic. This gets to an intriguing aspect of hardcoding types: You lose the flex, mostly. types are still better - the alternative is that you reliably attempt to write the same logic (or at least a call to some logic) to disentangle this mess every time you do anything with a string you happen to know is an email address which is terrible but gives you the option of intentionally not doing that if you don't want to apply the usual logic. That's no way to program, and thus actual types and the general trend that comes with it (namely: We do this right, we write that once, and there is no flexibility left). Programming is too hard to leave room for exotic cases that programmers aren't going to think about when dealing with this concept. And if you do need to deal with it, it can still be encoded in the type, but that then makes visible things that in untyped systems are invisible (if my email type only has a '.compare(boolean caseSensitive)' style method, and is not itself inherently comparable because of the case sensitivity thing, that makes it _seem_ much more complicated than plain old strings. This is a lie - emails in strings *IS* complicated. They just are. You can't make that go away. But you can hide it, and shoving all data in overly generic data types (numbers and strings) tends to do that.
- masklinn 8mo ago> it feels like sometime after Java got popular [...] a large chunk of the collective programming community forgot why strong static type checking was invented and are now having to rediscover this. I think you have a very rose-tinted view of the past: while on the academic side static types were intended for proof on the industrial side it was for efficiency. C didn't get static types in order to prove your code was correct, and it's really not great at doing that, it got static types so you could account for memory and optimise it. Java didn't help either, when every type has to be a separate file the cost of individual types is humongous, even more so when every field then needs two methods. > In most strong statically typed languages, you wouldn't often pass strings and generic dictionaries around. In most strong statically typed languages you would not, but in most statically typed codebases you would. Just look at the Windows interfaces. In fact while Simonyi's original "apps hungarian" had dim echoes of static types that got completely washed out in system, which was used widely in C++, which is already a statically typed language.
- guerrilla 8mo ago> I think you have a very rose-tinted view of the past I think they also forgot the entire Perl era.
- esafak 8mo agoThat's understandable. Youthful indiscretion is best forgotten.
- zahlman 8mo agoI can still remember trying to deal with structured binary data in Perl, just because I didn't want to fiddle around with memory management in C. I'm not sure it was actually any less painful, and I ultimately abandoned that first attempt. (Decades later, my "magnum opus" has been through multiple mental redesigns and unsatisfactory partial implementations. This time, for sure...)
- chriswarbo 8mo ago> You'd naturally gravitate towards parsing/transforming raw data into typed data structures that have guaranteed properties instead to avoid writing defensive code everywhere e.g. a Date object that would throw an exception in the constructor if the string given didn't validate as a date It's tricky because `class` conflates a lot of semantically-distinct ideas. Some people might be making `Date` objects to avoid writing defensive code everywhere (since classes are types), but... Other people might be making `Date` objects so they can keep all their date-related code in one place (since classes are modules/namespaces, and in Java classes even correspond to files). Other people might be making `Date` objects so they can override the implementation (since classes are jump tables). Other people might be making `Date` objects so they can overload a method for different sorts of inputs (since classes are tags). I think the pragmatics of where code lives, and how the execution branches, probably have a larger impact on such decisions than safety concerns. After all, the most popular way to "avoid writing defensive code everywhere" is to.... write unsafe, brittle code :-(
- munificent 8mo ago> You'd naturally gravitate towards parsing/transforming raw data into typed data structures that have guaranteed properties instead to avoid writing defensive code everywhere e.g. There's nothing natural about this. It's not like we're born knowing good object-oriented design. It's a pattern that has to be learned, and the linked article is one of the well-known pieces that helped a lot of people understand this idea.
- thom 8mo agoMy experience was that enterprise programmers burned out on things like WSDL at about the same time Rails became usable (or Django if you’re that way inclined). Rails had an excellent story for validating models which formed the basis for everything that followed, even in languages with static types - ASP.NET MVC was an attempt to win Rails programmers back without feeling too enterprisey. So you had these very convenient, very frameworky solutions that maybe looked like you were leaning on the type system but really it was all just reflection. That became the standard in every language, and nobody needed to remember “parse don’t validate” because heavy frameworks did the work. And why not? Very few error or result types in fancy typed languages are actually suited for showing multiple (internationalised) validation errors on a web page. The bitter lesson of programming languages is that whatever clever, fast, safe, low-level features a language has, someone will come along and create a more productive framework in a much worse language. Note, this framework - perhaps the very last one - is now ‘AI’.
- jiehong 8mo agoAnd then clojure enters: let’s keep few data structures but with tons of method. So things stay as maps or arrays all the way through.
- renox 8mo agoI worked (a long time ago) on a C project where every int was wrapped in a struct. And a friend told me about a C++ project where every index is a uint8, uint16, and they have to manage many different type of objects leading to lots of bugs.. So it isn't really linked to the language.