Earlier quoted context omitted.
Using basic types for domain concepts is called 'primitive obsession'. It's been considered code smell for at least 25 years. So this would be... not being primitive obsessed. It isn't anything driven development. Different people draw the line in different places for this. I've never tried writing code that takes every domain concept, no matter how small, and made a type out of it. It's always been on my bucket list…
I think often times it's enough to have enums for known ints, for example and have some parameter checking for ranges when known. Some languages like C++ made a contracts concept where you could make these checks more formal. As some people indicated the auto casting in many languages could make the implementation of these primitive based types complicated and fragile and provide more nuisance than it provides value.
Use Your Type System
51–60 of 357 posts
Re: Use Your Type System
#52(Typescript's Zed and Clojure's Malli are counterexamples. Although not official offerings)
Following OP's example, what prevents you from getting a AccountID parsed as a UserID at runtime, in production? In production it's all UUIDs, undistinguishable from one another.
A truly safe approach would use distinct value prefixes – one per object type. Slack does this I believe.
Re: Use Your Type System
#53An adjacent point is to use checked exceptions and to handle them appropriate to their type. I don't get why Java checked exceptions were so maligned. They saved me so many headaches on a project where I forced their use as I was the tech lead for it. Everyone hated me for a while because it forced them to deal with more than just the happy path but they loved it once they got in the rhythm of thinking about all the…
Re: Use Your Type System
#54Most static type systems that I know of disappear at runtime. You literally cannot "use" them once deployed to production. (Typescript's Zed and Clojure's Malli are counterexamples. Although not official offerings) Following OP's example, what prevents you from getting a AccountID parsed as a UserID at runtime, in production? In production it's all UUIDs, undistinguishable from one another. A truly safe approach woul…
That's part of the point of being static. If we can statically determine properties of the system and use that information in the derived machine code (or byte code or whatever), then we may be able to discard that information at runtime (though there are reasons not to discard it).
> Following OP's example, what prevents you from getting a AccountID parsed as a UserID at runtime, in production? In production it's all UUIDs, undistinguishable from one another.
If you're receiving information from the outside and converting it into data in your system you have to parse and validate it. If the UUID does not correspond to a UserID in your database or whatever, then the attempted conversion should fail. You'd have a guard like this:
if user_db.contains(UserID(uuid)) {
return UserID(uuid)
}
// signal an error or return a None, zero value, null, etc.Re: Use Your Type System
#55I like this. Very much falls into the "make bad state unrepresentable". The issues I see with this approach is when developers stop at this first level of type implementation. Everything is a type and nothing works well together, tons of types seem to be subtle permutations of each other, things get hard to reason about etc. In systems like that I would actually rather be writing a weakly typed dynamic language like…
Yep. For this reason, I wish more languages supported bound integers. Eg, rather than saying x: u32, I want to be able to use the type system to constrain x to the range of [0, 10). This would allow for some nice properties. It would also enable a bunch of small optimisations in our languages that we can't have today. Eg, I could make an integer that must fall within my array bounds. Then I don't need to do bounds ch…
Re: Use Your Type System
#56An adjacent point is to use checked exceptions and to handle them appropriate to their type. I don't get why Java checked exceptions were so maligned. They saved me so many headaches on a project where I forced their use as I was the tech lead for it. Everyone hated me for a while because it forced them to deal with more than just the happy path but they loved it once they got in the rhythm of thinking about all the…
I think checked exceptions were maligned because they were overused. I like that Java supports both checked and unchecked exceptions. But IMO checked exceptions should only be used for what Eric Lippert calls "exogenous" exceptions [1]; and even then most of them should probably be converted to an unchecked exception once they leave the library code that throws them. For example, it's always possible that your DB cou…
Re: Use Your Type System
#57Type 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 sy…
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!
Re: Use Your Type System
#58I was doing this and used it for a year in https://github.com/bbkane/warg/ , but ripped it out since Go auto-casts underlying types to derived types in function calls: Type userID int64 func Work(u userID) {...} Work(1) // Go accepts this I think I recalled that correctly. Since things like that were most of what I was doing I didn't feel the safety benefit in many places, but had to remember to cast the type in othe…
Go will not automatically cast a variable of one type to another. That still has to be done explicitly.
func main() {
var x int64 = 1
Func(SpecialInt64(x)) // this will work
Func(x) // this will not work
}
type SpecialInt64 int64
func Func(x SpecialInt64) {
}
https://go.dev/play/p/4eNQOJSmGqDRe: Use Your Type System
#59I was doing this and used it for a year in https://github.com/bbkane/warg/ , but ripped it out since Go auto-casts underlying types to derived types in function calls: Type userID int64 func Work(u userID) {...} Work(1) // Go accepts this I think I recalled that correctly. Since things like that were most of what I was doing I didn't feel the safety benefit in many places, but had to remember to cast the type in othe…
When you write 42 in Go, it’s not an int32 or int64 or some more specific type. It’s automatically inferred to have the correct type. This applies even for user-defined numeric types.
Re: Use Your Type System
#60In C#, I often use a type like: readonly struct Id32 { public readonly int Value { get; } } Then you can do: public sealed class MFoo { } public sealed class MBar { } And: Id32 x; Id32 y; This gives you integer ids that can’t be confused with each other. It can be extended to IdGuid and IdString and supports new unique use cases simply by creating new M-prefixed “marker” types which is done in a single line. I’ve als…
The name means "Value Object Generator" as it uses Source generation to generate the "Value object" types.
That readme has links to similar libraries and further reading.