The type system is a programmer's best friend(dusted.codes)
dusted.codes
The type system is a programmer's best friend
https://dusted.codes/the-type-system-is-a-programmers-best-friend
454 comments
> I want that data type to have helpful methods such as .Domain() or .NonAliasValue() which would return gmail.com and [email protected] respectively for an input of [email protected].
No the hell you don't.
Please please please do not attempt to separate the alias from an email address I submit. It's there for a reason - specifically, to hold you accountable if I experience a sudden influx of spam, and generally to keep things categorized in a world where senders can be sending things from all sorts of domains. Knowing that this is something one would even remotely consider is grounds to never touch anything one has built with a ten-foot pole, and I am now very strongly inclined to look into the author and compulsively scrub any accounts of mine from anything said author might've touched.
I am not exaggerating. The thing before the @ is meant to be opaque. Deeming otherwise for the sake of something so blatantly user-hostile as removing aliases is plain evil, and I will not sugarcoat my condemnation of such practices.
If you're sufficiently sociopathic to have no regard for the morality argument here, then at the very least take heed of RFC 5322 (https://datatracker.ietf.org/doc/html/rfc5322) and recognize that trying to parse any meaning from an email address' local-part is blatantly ignorant of IETF specifications and almost certainly will create bugs. Just don't do it - if not for your users' sake, then for your own.
No the hell you don't.
Please please please do not attempt to separate the alias from an email address I submit. It's there for a reason - specifically, to hold you accountable if I experience a sudden influx of spam, and generally to keep things categorized in a world where senders can be sending things from all sorts of domains. Knowing that this is something one would even remotely consider is grounds to never touch anything one has built with a ten-foot pole, and I am now very strongly inclined to look into the author and compulsively scrub any accounts of mine from anything said author might've touched.
I am not exaggerating. The thing before the @ is meant to be opaque. Deeming otherwise for the sake of something so blatantly user-hostile as removing aliases is plain evil, and I will not sugarcoat my condemnation of such practices.
If you're sufficiently sociopathic to have no regard for the morality argument here, then at the very least take heed of RFC 5322 (https://datatracker.ietf.org/doc/html/rfc5322) and recognize that trying to parse any meaning from an email address' local-part is blatantly ignorant of IETF specifications and almost certainly will create bugs. Just don't do it - if not for your users' sake, then for your own.
Articles like this bug me. You've given me a list of why types are awesome. Great. Now, tell me what the tradeoff is. Nothing is free in engineering. To get something, you have to give up something else. Even grug[0] understands this.
[0]: https://grugbrain.dev/#grug-on-type-systems
[0]: https://grugbrain.dev/#grug-on-type-systems
I have to plug Ada's rich type system for explicitly encouraging this kind of design. With things like type predicates [1], you can do run-time enforcement or even prove at compile-time (to optimize away the runtime checks) that type constraints are met.
As an example of this, in a piece of code I'm working on there's a Base64_String type, where only RFC 4648 characters are permitted to be part of the string, the '=' padding character can only appear at the end of the string, and if the second-to-last padding byte is '=' then the last one must be as well. This is all enforced by the type system without having to call "validate()" or something every time its used.
1. https://learn.adacore.com/courses/intro-to-ada/chapters/cont...
As an example of this, in a piece of code I'm working on there's a Base64_String type, where only RFC 4648 characters are permitted to be part of the string, the '=' padding character can only appear at the end of the string, and if the second-to-last padding byte is '=' then the last one must be as well. This is all enforced by the type system without having to call "validate()" or something every time its used.
1. https://learn.adacore.com/courses/intro-to-ada/chapters/cont...
Oh god, please just use primitive types. Don't make assumptions about things.
Everyone thinks they are so smart validating emails, phone numbers, zip codes and all until their great design goes live and they discover that users in the real world do not follow their assumptions.
I have seen that happen again and again. No, if your idea of validating an email is more complicated than "should have an @ symbol", I guarantee you, there is counter example that will mess you pretty system up. Have fun scrambling to fix that ticket.
Oh you think you know how an address and zip code should look like? No, you don't.
Please people, just use strings and call it a day. Why do you like to suffer?
I mean, sure, using the type system to protect you from mixing up units can be useful. Everything in moderation. Primitive types are good types.
Everyone thinks they are so smart validating emails, phone numbers, zip codes and all until their great design goes live and they discover that users in the real world do not follow their assumptions.
I have seen that happen again and again. No, if your idea of validating an email is more complicated than "should have an @ symbol", I guarantee you, there is counter example that will mess you pretty system up. Have fun scrambling to fix that ticket.
Oh you think you know how an address and zip code should look like? No, you don't.
Please people, just use strings and call it a day. Why do you like to suffer?
I mean, sure, using the type system to protect you from mixing up units can be useful. Everything in moderation. Primitive types are good types.
I'm learning Python after 35 years of working with statically typed languages (Pascal, C++, Java, a bit of Typescript lately) and by god this is hard. Not because there is anything in the language that I don't understand but the lack of any type info is killing me. I just can't build up a rhythm of coding. I feel like every five lines I have to sprinkle in print() statements to keep track of the data transformations as there is nothing useful that can be captured about it even with these weak ass "type hints". I know that even when I get this crap to work I'll hate going back to that code in a few months as I'll have forgotten what the hell it all did and will have to spike it with print() ad df.shape() again to make sense of it. And naming discipline can only get you so far.
Maybe dynamic typing just isn't my thing and I need a new gig...
Maybe dynamic typing just isn't my thing and I need a new gig...
> grug very like type systems make programming easier. for grug, type systems most value when grug hit dot on keyboard and list of things grug can do pop up magic. this 90% of value of type system or more to grug
> danger abstraction too high, big brain type system code become astral projection of platonic generic turing model of computation into code base. grug confused and agree some level very elegant but also very hard do anything like record number of club inventory for Grug Inc. task at hand
https://grugbrain.dev/#grug-on-type-systems
> danger abstraction too high, big brain type system code become astral projection of platonic generic turing model of computation into code base. grug confused and agree some level very elegant but also very hard do anything like record number of club inventory for Grug Inc. task at hand
https://grugbrain.dev/#grug-on-type-systems
I once attended a meeting where a Professor from a University somewhere in Chicago gave a brilliant demonstration of using a similar type system for dealing with values in Electrical Engineering. It made quite sure you couldn't do things like add volts and amps.
[Edit] it also handled things like parallel resistances, etc.
It was in C++ if I recall correctly.
This is a great idea, that I've haven't had cause to use yet.
[Edit] it also handled things like parallel resistances, etc.
It was in C++ if I recall correctly.
This is a great idea, that I've haven't had cause to use yet.
The most successful languages are typed but weakly so. Just enough type system to avoid the biggest class of bugs, not enough to get in your way all the time. Golang strikes this balance very well. Too little typing, and your Python unit tests get too heavy to run after every commit. Too much, and you have to read a book on category theory before you can figure out how to grab that one field using Lenses in Haskel.
Edit: I should note this is coming from an outside observer as my most favorite languages are dynamically typed like Python and Lisp. But it should also be noted that I like writing in small code bases. Larger ones tend to need typing.
Edit: I should note this is coming from an outside observer as my most favorite languages are dynamically typed like Python and Lisp. But it should also be noted that I like writing in small code bases. Larger ones tend to need typing.
> ... to prevent silly mistakes like multiplying $100 with £20
Honest question, what should multiplying $100 with $20 give?
Honest question, what should multiplying $100 with $20 give?
Hey author here! Sorry I didn't respond to any feedback yet. I've literally posted this before leaving my house and didn't think it would get many upvotes as it didn't get any votes the other day either.
Sorry that the general sentiment is "everything old gets new again". I didn't try to rehash some old news again. I basically blog about things that come up in my daily work life and this topic was something that I felt quite passionately about. From my own experience I felt that type systems, especially in modern languages, are not nearly as well utilised as they could be. Of course there is always a balance to strike, especially with over engineering and needless optimisations, but that is a topic for another blog post another day.
Sorry that the general sentiment is "everything old gets new again". I didn't try to rehash some old news again. I basically blog about things that come up in my daily work life and this topic was something that I felt quite passionately about. From my own experience I felt that type systems, especially in modern languages, are not nearly as well utilised as they could be. Of course there is always a balance to strike, especially with over engineering and needless optimisations, but that is a topic for another blog post another day.
>A string value is not a great type to convey a user's email address or their country of origin.
So we have a type for "country of origin". And then some country that you have in the records splits up into 2 countries, what do you do then? Do you keep a list of all countries that ever existed and keep it up to date?
This approach works good in some cases, but not always
So we have a type for "country of origin". And then some country that you have in the records splits up into 2 countries, what do you do then? Do you keep a list of all countries that ever existed and keep it up to date?
This approach works good in some cases, but not always
I don't understand. Isn't classes what the author asks for?
>A string value is not a great type to convey a user's email address or their country of origin. These values deserve much richer and dedicated types
this is a classic case of not needing more types but needing proper names. Types as concretions, i.e. simply collections of data or functions are a terrible idea because they're static and don't accrete. Data in the real world always does. This becomes very obvious when you go down a paragraph and you see the conundrum:
>For example, let's have a second type called VerifiedEmailAddress. If you wish it can even inherit from an EmailAddress. I don't care, but ensure that there is only one place in the code which can yield a new instance of VerifiedEmailAddress
okay, and for the next email setup let's have a third type, and a fourth type, and a fifth type, and so on. The end result of this is a zoo of types that help nobody to understand anything. It reminds me of an older Rich Hickey talk. When you program a delivery truck you don't make a type for each different truck because of the contents of the truck, you just take your delivery out of the truck and you don't care about the rest.
this is a classic case of not needing more types but needing proper names. Types as concretions, i.e. simply collections of data or functions are a terrible idea because they're static and don't accrete. Data in the real world always does. This becomes very obvious when you go down a paragraph and you see the conundrum:
>For example, let's have a second type called VerifiedEmailAddress. If you wish it can even inherit from an EmailAddress. I don't care, but ensure that there is only one place in the code which can yield a new instance of VerifiedEmailAddress
okay, and for the next email setup let's have a third type, and a fourth type, and a fifth type, and so on. The end result of this is a zoo of types that help nobody to understand anything. It reminds me of an older Rich Hickey talk. When you program a delivery truck you don't make a type for each different truck because of the contents of the truck, you just take your delivery out of the truck and you don't care about the rest.
This "everything old is new again" stuff gets a bit much sometimes.
Of course having ability to do strict typing is a good way to avoid bugs and write reliable code. This is not news.
Of course having ability to do strict typing is a good way to avoid bugs and write reliable code. This is not news.
You know those "how to draw Bugs Bunny" art guides they used to include in children's art books? Where they begin with a circle with some guidelines and then do a whole bunch of stuff and the end result is Bugs Bunny? But you have no idea how they went from Point A to Point B? That's the same thing with Type Theory.
PROFESSOR: Well, you see, there are different objects, like strings and numbers, that are shaped differently, so we put them into different categories. Those are types! See how simple this is?
<a whole bunch of stuff later>
PROFESSOR: Now the endofunctor of the covariant types are jointly distributed under the free monoid, provided that the pullback doesn't reverberate the time fibration of the dependent type space.
PROFESSOR: Well, you see, there are different objects, like strings and numbers, that are shaped differently, so we put them into different categories. Those are types! See how simple this is?
<a whole bunch of stuff later>
PROFESSOR: Now the endofunctor of the covariant types are jointly distributed under the free monoid, provided that the pullback doesn't reverberate the time fibration of the dependent type space.
There is an important difference between types and objects: types are basic building blocks. An email is not a basic building block nor is money. In the mature ecosystem of Java there are libs and standard libraries that handles these problems some are even in the standard library (timestamp with timezone, url...)
It would be nice to have stuff for these in the standard libraries but currencies are a moving target and needs constant update to handle the quirks of the real world. For the same reason it's extremely hard to create an "Address" class that handles every possible scenario.
In my book the string is a great way to store a country of origin because everything else depends on the usage of that information.
It would be nice to have stuff for these in the standard libraries but currencies are a moving target and needs constant update to handle the quirks of the real world. For the same reason it's extremely hard to create an "Address" class that handles every possible scenario.
In my book the string is a great way to store a country of origin because everything else depends on the usage of that information.
The problem with this is the explosion of types when you start with combinations. Stuff like VerifiedEmailOptedOutOfMarketing.
I can see a type system designed from the ground up to do something like this. But as a bolt on to existing languages it's pretty clunky.
I can see a type system designed from the ground up to do something like this. But as a bolt on to existing languages it's pretty clunky.
Why is it better to have two email types, VerifiedEmail and UnverifiedEmail vs. one Email type with an "isVerified" field?
One type is probably going to align with storage and transport better, and you probably mostly want to treat verified and unverified email addresses the same except for some very specific situations. (E.g., maybe only your EmailBlaster cares, where it's like a privilege: some can send to unverified emails and some can't.)
This seems like a bad for types to me.
(It's all code you write and data you design -- types are just one tool... you need to think about they best tool, not get fixated on one, no matter nice it is.)
One type is probably going to align with storage and transport better, and you probably mostly want to treat verified and unverified email addresses the same except for some very specific situations. (E.g., maybe only your EmailBlaster cares, where it's like a privilege: some can send to unverified emails and some can't.)
This seems like a bad for types to me.
(It's all code you write and data you design -- types are just one tool... you need to think about they best tool, not get fixated on one, no matter nice it is.)
>4 hours ago
Ah, you reposted this, hoping the weekend crowd might be kinder. Cheeky lad.
Ah, you reposted this, hoping the weekend crowd might be kinder. Cheeky lad.
I'm working in a codebase that has, at times, 10+ different expressions within a single conditional in many places, and trying to pull out the context of why the conditional exists in the first place make grug brain hurt. At the very least, you could put all of the expressions and assign to a boolean with a variable name saying wtf it is you're conditioning on.
https://grugbrain.dev/#grug-on-expression-complexity
https://grugbrain.dev/#grug-on-expression-complexity
> A string value is not a great type to convey a user's email address or their country of origin. These values deserve much richer and dedicated types. I want a data type called EmailAddress which cannot be null.
Sure, I'm on board: I also want an e-mail address type. Just not in your shit language in which something can be of type String, yet be null reference.
Sure, I'm on board: I also want an e-mail address type. Just not in your shit language in which something can be of type String, yet be null reference.
Type systems cause programmers to write 300 classes no one references and many duplicates of each other.
Dynamic typing allows you to focus on what really matters, not trivial business logic OOP hierarchies that get inevitably ignored.
I feel like in app development, there’s something honest about dynamic typing. You’re focusing on the instance rather than the unnecessary model definition that again, nobody uses and redefined elsewhere anyway.
Tbh, I’ve never used a language like JS professionally. I’m sure a lot of code bases are copied and pasted messes with state dependencies and things that make it so you HAVE to focus on the type. It’s an app language, you’re just not gonna find elegance lol.
I’ve been a C# dev for a while now. I’ve just found that outside of libraries/frameworks/etc how rarely object model code actually gets reused. And I appreciate the cut to the chase aspect of that.
Insert quote about gorilla with banana in forest.
Dynamic typing allows you to focus on what really matters, not trivial business logic OOP hierarchies that get inevitably ignored.
I feel like in app development, there’s something honest about dynamic typing. You’re focusing on the instance rather than the unnecessary model definition that again, nobody uses and redefined elsewhere anyway.
Tbh, I’ve never used a language like JS professionally. I’m sure a lot of code bases are copied and pasted messes with state dependencies and things that make it so you HAVE to focus on the type. It’s an app language, you’re just not gonna find elegance lol.
I’ve been a C# dev for a while now. I’ve just found that outside of libraries/frameworks/etc how rarely object model code actually gets reused. And I appreciate the cut to the chase aspect of that.
Insert quote about gorilla with banana in forest.
Question: isn’t a rich type system as described here similar to OOP?
>A string value is not a great type to convey a user's email address or their country of origin.
I can argue whatever type you use in place of a string will similarly be "not a great type". This can be argued in perpetuity because no type/map actually matches the reality it's encapsulating.
Type systems require you to build a Pretty Good Theory of your problem space so that your types can overlap with reality/actual usage as much as possible.
The problem is at planning/design time, you'll have a Pretty Crappy Theory of your problem space and can only get a Pretty Good one after having wrestled with it for a while.
A dynamic, more forgiving language, allows you to build what you can today with the theory you have, and then change it in the future when your theory gets disproven.
I can argue whatever type you use in place of a string will similarly be "not a great type". This can be argued in perpetuity because no type/map actually matches the reality it's encapsulating.
Type systems require you to build a Pretty Good Theory of your problem space so that your types can overlap with reality/actual usage as much as possible.
The problem is at planning/design time, you'll have a Pretty Crappy Theory of your problem space and can only get a Pretty Good one after having wrestled with it for a while.
A dynamic, more forgiving language, allows you to build what you can today with the theory you have, and then change it in the future when your theory gets disproven.
Started a Ruby project using Sorbet and I'm refactoring with confidence now. It's such a huge help. The project is about 9000 lines of Ruby by now and I don't think I would have been able to get this far without static type checking
100% agree. I love C++ for allowing me to do things like:
CInches in1, in2, in3; // basically just floating points
CCm cm1; // basically just floating points
in1 = in2 + in3; // no compilation error
in1 = in2 + cm1; // compilation error
in1 = in2 * in3; // compilation error
in1 = 2. * in2; // no compilation error
float foo(CInches *);
foo(cm1); // compilation error
Obviously there's a lot of code behind "CInches", but it catches so many errors.
Which other languages also support this?I’ve always grokked primitive types as mapping to different concepts used when storing values in memory.
A string is some bytes in a line with a terminator at the end.
An integer is a group of signed or unsigned bytes.
An enum value points at another value with a pointer.
Etc.
What I think this describes is some validation classes, which don’t need to be built into a language’s runtime. Primitive types have a real reason for existing when compiling these apps, a validation class doesn’t. It can just be a library, in which case this is a nice API for validation!
A string is some bytes in a line with a terminator at the end.
An integer is a group of signed or unsigned bytes.
An enum value points at another value with a pointer.
Etc.
What I think this describes is some validation classes, which don’t need to be built into a language’s runtime. Primitive types have a real reason for existing when compiling these apps, a validation class doesn’t. It can just be a library, in which case this is a nice API for validation!
At the opening of the article, as the author describes a type, it ends up sounding like a class to me.
I summarize the article as "use OOP and design your classes well".
Every programmer who has ever single-handedly written a 100,000+ LOC software system will tell you the same thing: shift as much responsibility on the compiler as you can and have the compiler check the code you write to any extent technologically possible.
Getting rid of bugs by experiencing, diagnosing and fixing them takes at least ten times more effort than getting rid of bugs by not making them in the first place, through expressing the problem at hand with a strong type system.
When you also consider the never ending necessity to introduce change to an already written software system, thus the necessity to refactor code (in the sense of altering the previously assumed meaning of its idioms), the critical advantage of a strong type system becomes self-evident.
(Yes, Rust 4ev3r! ;))