![]() |
|
![]() |
| No tool can fully protect against user incompetence (pun intended). You can have the fanciest type system in the world and still implement everything as a String, for example. |
![]() |
| > You don't often need a VerifiedEmail. But VerifiedUser is more useful from VerifiedEmail. You could get an implicitly verified email without introducing a new type.
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. |
![]() |
| You don't need VerifiedUser in the moment user click verification link. It's a state changing action. You get verified user from db and work with it later. |
![]() |
| State machine is always a trivial part. Email verification is stupidly simple task. But User propagation through the system and available actions based on verified email are not. |
![]() |
| Whenever this comes up, I'm reminded of section 5 in https://cr.yp.to/qmail/guarantee.html which among other things says "Don't parse" and "there are two types of command interfaces in the world of computing: good interfaces and user interfaces".
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. |
![]() |
| > Is it possible to implement foo? Trivially, the answer is no, as Void is a type that contains no values, so it’s impossible for any function to produce a value of type Void
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. |
![]() |
| Haskell has this simply for historical reasons. It's a wart and we wish it weren't so but changing it would break a lot of legacy code (and probably have adverse performance impacts in some cases) so it remains. So as it is today, [a] indeed means it can be empty or not. And a function signature of "[a] -> a" is essentially an unsafe partial function.
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. |
![]() |
| It seems that CloudStrike only parsed and didn't validate, to great effect :-) /s
Not saying that this advice isn't solid, just thought it's funny given the news of this week. |
![]() |
| yeah. use raw datatype url provided by the language and get hacked by some exotic xss you are not aware, because the specs have kitchen and sink included |
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.