Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Type systems, like any other tool in the toolbox, have an 80/20 rule associated with them. It is quite easy to overdo types and make working with a library extremely burdensome for little to no to negative benefit.

I know what a UUID (or a String) is. I don't know what an AccountID, UserID, etc. is. Now I need to know what those are (and how to make them, etc. as well) to use your software.

Maybe an elaborate type system worth it, but maybe not (especially if there are good tests.)

https://grugbrain.dev/#grug-on-type-systems



> I don't know what an AccountID, UserID, etc. is. Now I need to know what those are (and how to make them, etc. as well) to use your software.

Presumably you need to know what an Account and a User are to use that software in the first place. I can't imagine a reasonable person easily understanding a getAccountById function which takes one argument of type UUID, but having trouble understanding a getAccountById function which takes one argument of type AccountId.


UserID and AccountID could just as well be integers.

What he means is that by introducing a layer of indirection via a new type you hide the physical reality of the implementation (int vs. string).

The physical type matters if you want to log it, save to a file etc.

So now for every such type you add a burden of having to undo that indirection.

At which point "is it worth it?" is a valid question.

You made some (but not all) mistakes impossible but you've also introduced that indirection that hides things and needs to be undone by the programmer.


Well...yeah. That's the point. We want there to be a layer of indirection to prevent mistakes. Otherwise you get things like this: https://www.columbia.edu/~ng2573/zuggybuggy_is_2scale4ios.pd...

> There is a UI for memorialising users, but I assured her that the pros simply ran a bit of code in the PHP debugger. There’s a function that takes two parameters: one the ID of the person being memorialised, the other the ID of the person doing the memorialising. I gave her a demo to show her how easy it was....And that’s when I entered Clowntown....I first realised something was wrong when I went back to farting around on Facebook and got prompted to login....So in case you haven’t guessed what I got wrong yet, I managed to get the arguments the wrong way round. Instead of me memorialising my test user, my test user memorialised me.


I recommend adding a serialization method to your types, namely to text, but optionally to JSON as well.


> I know what a UUID (or a String) is. I don't know what an AccountID, UserID, etc. is. Now I need to know what those are (and how to make them, etc. as well) to use your software.

Yes, that’s exactly the point. If you don’t know how to acquire an AccountID you shouldn’t just be passing a random string or UUID into a function that accepts an AccountID hoping it’ll work, you should have acquired it from a source that gives out AccountIDs!


And that's my point: I'm usually getting AccountIDs from strings (passed in via HTTP requests) so the whole thing becomes a pointless exercise.


You just accept raw strings without doing any kind of validation? The step that performs validation should encode that step in the form of a type.


i pride myself in never doing any validation ever

never escape anything, either

just hand my users a raw SQL connection


I prefer to just skip a few steps and email them my bank account number when they register an account.


Do you validate them? I assume you do. Feels like a great time to cast them too


'Parse, Don't Validate'



Validating and then casting to a type sounds an awful lot like parsing.


If your system is full of stringly typed network interfaces then yes there is no point in trying to make it good. You can make things a bit better by using a structured RPC protocol like gRPC, but the only real solution is to not do that.


> I know what a UUID (or a String) is.

I now know I never know whenever "a UUID" is stored or represented as a GUIDv1 or a UUIDv4/UUIDv7.

I know it's supposed to be "just 128 bits", but somehow, I had a bunch of issues running old Java servlets+old Java persistence+old MS SQL stack that insisted, when "converting" between java.util.UUID to MS SQL Transact-SQL uniqueidentifier, every now and then, that it would be "smart" if it flipped the endianess of said UUID/GUID to "help me". It got to a point where the endpoints had to manually "fix" the endianess and insert/select/update/delete for both the "original" and the "fixed" versions of the identifiers to get the expected results back.

(My educated guess it's somewhat similar to those problems that happens when your persistence stack is "too smart" and tries to "fix timezones" of timestamps you're storing in a database for you, but does that wrong, some of the time.)


UUIDs all have the same storage+representation.

They are generated with different algorithms, if you find these distinctions to be semantically useful to operations, carry that distinction into the type.

Seems like 98% of the time it wouldn’t matter.


foo(UUID, UUID); foo(AccountId, UserId);

I'd much rather deal with the 2nd version than the first. It's self-documenting and prevents errors like calling "foo(userId, accountId)" letting the compiler test for those cases. It also helps with more complex data structures without needing to create another type.

  Map<UUID, List<UUID>>
  Map<AccountId, List<UserId>>


> I know what a UUID (or a String) is. I don't know what an AccountID, UserID, etc. is.

It's literally the opposite. A string is just a bag of bytes you know nothing about. An AccountID is probably... wait for it... an ID of an Account. If you have the need to actually know the underlying representation you are free to check the definition of the type, but you shouldn't need to know that in 99% of contexts you'll want to use an AccountID in.

> Now I need to know what those are (and how to make them, etc. as well) to use your software.

You need to know what all the types are no matter what. It's just easier when they're named something specific instead of "a bag of bytes".

> https://grugbrain.dev/#grug-on-type-systems

Linking to that masterpiece is borderline insulting. Such a basic and easy to understand usage of the type system is precisely what the grug brain would advocate for.


The OP is the author of grugbrain.dev


To be fair, you probably needed to know that anyway? Or else you would've just passed invalid data into functions.


I cannot recall ever passing an invalid UUID (or long id) into a function due to statically-knowable circumstances.


The point is that you might pass a semantically invalid user ID. Not that you might pass an invalid UUID.

I generally agree that it's easy to over-do, but can be great if you have a terse, dense, clear language/framework/docs, so you can instantly learn about UserID.


More specifically, if all entities have a GUID, it's not impossible to accidentally map entity A ID to entity B ID accidentally, especially when working with relationships. Moving the issue to the compiler is nicer than the query returning 0 results and the developer staring endlessly for the subtle issue.


I think the example is just not very useful, because it illustrates a domain separation instead of a computational one, which is almost always the wrong approach.

It is however useful to return a UUID type, instead of a [16]byte, or a HTMLNode instead of a string etc. These discriminate real, computational differences. For example the method that gives you a string representation of an UUID doesn't care about the surrounding domain it is used in.

Distinguishing a UUID from an AccountID, or UserID is contextual, so I rather communicate that in the aggregate. Same for Celsius and Fahrenheit. We also wouldn't use a specialized type for date times in every time zone.


There are a few languages where this is not too tedious (although other things tend to be a bit more tedious than needed in those)

The main problem with these is how do you actually get the verification needed when data comes in from outside the system. Check with the database every time you want to turn a string/uuid into an ID type? It can get prohibitively expensive.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: