4 ms·
The type system is a programmer's best friend
- necovek 4y agoWow, this is completely ill-informed. Lets run with the example they give of a normalised, validated EmailAdress field. If you use that, your user enters a value, and something else is stored and returned the next time. They think they did something wrong and they don't care to learn that email addresses are (mostly) case-insensitive, so they try to re-update their email address to First.LAST@gmail.com repeatedly, then reach out to support to try to get that fixed (or give up on such a lousy product). Or relying on the fact that +whatever indicates an alias (a gmail convention, and not a standard at all): on my own server, I could have an entirely different convention. They sure want that EmailAddress field to understand the gmail convention of a dot being a "formatting" character with no semantics either, when that's just another convention (and the one even gmail got wrong early on, so look up the horror stories of people having separate accounts from the early days with just dots in a different place). And there's now a problem with parsing the address, and you need to surface the exact problem: but how can a user see it from all the normalisation you just "helpfully" did? And why is EmailAddress not consisted of a pair of InternetDomain and EmailAccountName instead? (Ofc, InternetDomain is a list of InternetDomains, so you can tell what a TLD is and whether it's a country domain or maybe a deprecated country domain, or if there are aliases for a domain or...). I am sure I can come up with more issues with this approach: this is a begginers fascination with OOP where they are still deceived by the promise that the world can be represented with nicely structured "objects". What you need to do instead is have an ability to accept any user input, and there's a generic type that works great for the task: a string! There is value in having some layers only accept validated and normalised input (you can avoid any error checking there of you are brave ;), but custom, "smart" types do not add much value and require too much work for no gain.
- dustedcodes 4y agoHey author here. I don't think I'm ill informed. Most users understand that when they enter FoObAr@gmail.com somewhere that they might see foobar@gmail.com as their account somewhere. Literally every service I use does that. For years I used to enter my email address as FirstnameLastname@gmail.com and all those systems (Google products, and more) show me firstnamelastname@gmail.com. Heck, even my email provider shows my email as lowercase when I receive an email. The other thing about the alias it's clear you didn't read what I wrote. I didnt' say that a user's email address should get modified in the system and the +bar should get cut out. I was saying that it would be a handy feature to have a type which understands these things so you can run a search against your dataset if you want to match up people. In some systems that might make sense, in others not. Nobody forces you to apply the business rules of someone else. Besides you seem to fundamentally misunderstand something here. > And there's now a problem with parsing the address, and you need to surface the exact problem: but how can a user see it from all the normalisation you just "helpfully" did? How a system thinks of an object internally and how it is displayed to the user are two different things. In fact, every well designed software does what I suggested in a variety of places. When I register somewhere with foobar@gmamil.com but then a year later use the password reset function and enter Foobar@gmail.com then I will receive a password reset link as anyone would expect. Find me one popular software that doesn't do that. Anything else would be an awful user experience. Anyhow, the point is that YOU design your types the way they help to accomplish YOUR goals. I just gave some silly examples to demonstrate how one can use types to do useful things. > this is a begginers fascination with OOP Actually what I described in my blog post is bread and butter in FP and personally I consider myself more of an FP developer as I've been doing mostly F# for many years now but don't let the facts distract you from being unnecessarily rude and offensive for no apparent reason.
- necovek 4y agoThank you for responding. I never said either that you are "normalizing away" the +bar thing, but that you are building in a non-standard convention to parse semantics of an email address which (mostly) hold true for one particular provider (gmail.com), without at all acknowledging that this is gmail-specific. Even if you are aware of it, your readers might not be. In short, I do agree that your examples are silly, but I disagree that you demonstrate how they can do useful things: you mostly show how you can do silly things with them instead. Note that you are suggesting this as a pattern: even if your users are accustomed to their email addresses being normalized, they might not be accustomed to other things being so. If one takes your advice, you'd have a FullName, FirstName, LastName types as well which does the normalization based on the language, and using eg. Unicode NF. I do agree that you should do the things that have value, but I don't see you showing that value anywhere (as your example has a bunch of problems I highlighted, yet the benefits are?). Note that I've been there as well early on in my career: it feels so satisfying to imagine a world where our data types smartly represent exactly the object they need to, with all the properties and actions one can do with it. However, it quickly breaks up in the real world, and you end up with IncomingEmailAddress, SMTPEnvelopeEmailAddress, UserProvidedEmailAddress etc instead. This is not to say that if you've got a specific application that does some smart handling of an email address (like having aliasing rules for different popular providers) — eg. a mailing list app — but if that's not your business domain, a string is usually a better choice for what is essentially a string. Let me give you another simple yet convoluted example that usually quickly breaks: compund types (even your EmailAddress would be used in different contexts). For simplicity, let's go to my example of Name: (FirstName, LastName). For display purposes, you might want to show Name as "<FirstName> <LastName>" on a profile page (except for East Asian names), but collate according to LastName. Now your Name needs to understand the context it's in, and what you hoped would be a trivial semantic structure is anything but. All of this is to say: keep your data structures as simple as possible for as long as you can. Your future self will thank you.
- activitypea 4y ago> Note: According to the official RFC the part of an email address before the @-sign could be case-sensitive, but all major email hosts treat them as case-insensitive and so it's not unreasonable for a domain type to take this knowledge into consideration. I don't think that's how types work.
- activitypea 4y agoI don't quite understand the VerifiedEmailAddress example. Seems like you're just moving Address(verified: true) into the name. But since both of the email types aren't types but rather containers for a string, they're not interchangeable in nominal nor structural type systems, so you're creating a bunch of noisy code like `changePassword(new EmailAddress(verifiedEmailAddress.getValue())`. This is like the worst aspects of the "it's just data" approach combined with the worst aspects of encapsulation. > Using a primitive string for an object which could get so easily expressed through its own type is simply lazy and unimaginative programming. What a shallow and incurious take. Funny you should complain about "lazy programmers", when the only justification for this approach you present I would classify as gross negligence, if not grounds for termination. Who in the world would set "verified" to be true by default?