Live data from Hacker News

Use Your Type System

dzombak.com

41–50 of 357 posts

Re: Use Your Type System

#41

Earlier quoted context omitted.

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.

Re: Use Your Type System

#42
This technique makes me sad.

Not because it's a bad idea. Quite the contrary. I've sung the praises of it myself.

But because it's like the most basic way you can use a type system to prevent bugs. In both the sense used in the article, and in the sense that it is something you have to do to get the even more powerful tools brought to bear on the problem that type systems often.

And yet, in the real world, I am constantly explaining this to people and constantly fighting uphill battles to get people to do it, and not bypass it by using primitives as much as possible then bashing it into the strict type at the last moment, or even just trying to remove the types.

Here on HN we debate the finer points of whether we should be using dependent typing, and in the real world I'm just trying to get people to use a Username type instead of a string type.

Not always. There are some exceptions. And considered over my entire career, the trend is positive overall. But there's still a lot of basic explanations about this I have to give.

I wonder what the trend of LLM-based programming will result in after another few years. Will the LLMs use this technique themselves, or will people lean on LLMs to "just" fix the problems from using primitive types everywhere?

Re: Use Your Type System

#43
post #3

I 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…

FYI: Ruby is strongly typed, not loosely. > 1 + "1" (irb):1:in 'Integer#+': String can't be coerced into Integer (TypeError) from (irb):1:in ' ' from :168:in 'Kernel#loop' from /Users/george/.rvm/rubies/ruby-3.4.2/lib/ruby/gems/3.4.0/gems/irb-1.14.3/exe/irb:9:in ' ' from /Users/george/.rvm/rubies/ruby-3.4.2/bin/irb:25:in 'Kernel#load' from /Users/george/.rvm/rubies/ruby-3.4.2/bin/irb:25:in ' '

Oops, I meant weakly typed as in JS or strongly typed as in Ruby. But decided to switch the Ruby example to Elixir and messed up the sentence

Re: Use Your Type System

#45
post #37

An 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…

With Java, there are a lot of usability issues with checked types. For example streams to process data really don't play nicely if your map or filter function throws a checked exception. Also if you are calling a number of different services that each have their own checked exception, either you resort to just catching generic Exception or you end up with a comically large list of exceptions

Re: Use Your Type System

#46
post #38

Earlier quoted context omitted.

FYI: Ruby is strongly typed, not loosely. > 1 + "1" (irb):1:in 'Integer#+': String can't be coerced into Integer (TypeError) from (irb):1:in ' ' from :168:in 'Kernel#loop' from /Users/george/.rvm/rubies/ruby-3.4.2/lib/ruby/gems/3.4.0/gems/irb-1.14.3/exe/irb:9:in ' ' from /Users/george/.rvm/rubies/ruby-3.4.2/bin/irb:25:in 'Kernel#load' from /Users/george/.rvm/rubies/ruby-3.4.2/bin/irb:25:in ' '

irb(main):001:0> a = 1 => 1 irb(main):002:0> a = '1' => "1" It doesn't seem that strong to me.

In the dynamic world being able to redefine variables is a feature not a bug (unfortunately JS has broken this), even if they are strongly typed. The point of strong typing is that the language doesn't do implicit conversions and other shenanigans.

Re: Use Your Type System

#47
post #5

Does anyone know the term for this? I had "Type Driven Development" in my head, but I don't know if that's a broadly used term for this. It's a step past normal "strong typing", but I've loved this concept for a while and I'd love to have a name to refer to it by so I can help refer others to it.

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.

Re: Use Your Type System

#48
post #3

I 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…

I've recently been following red-green-refactor but instead of with a failing test, I tighten the screws on the type system to make a production-reported bug cause the type checker to fail before making it green by fixing the bug. I still follow TDD-with-a-test for all new features, all edge cases and all bugs that I can't trigger failure by changing the type system for. However, red-green-refactor-with-the-type-syst…

I like this approach, there are often calls for increased testing on big systems and what they really mean is increased rigor. Don't waste time testing what you can move into the compiler.

It is always great when something is so elegantly typed that I struggle to think of how to write a failing test.

What drives me nuts is when there are testing left around basically testing the compiler that never were “red” then “greened” makes me wonder if there is some subtle edge case I am missing.

Re: Use Your Type System

#49
post #3

I 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…

Also known as "make bad state unexperimentable".

Re: Use Your Type System

#50
post #37

An 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…

Setting aside the objections some have to exceptions generally: Checked exceptions, in contrast to unchecked, means that if a function/method deep in your call stack is changed to throw an exception, you may have to change many function (to at least denote that they will throw that exception or some exception) between the handler and the thrower. It's an objection to the ergonomics around modifying systems.

Think of the complaints around function coloring with async, how it's "contagious". Checked exceptions have the same function color problem. You either call the potential thrower from inside a try/catch or you declare that the caller will throw an exception.

Post reply on HN