Live data from Hacker News

Don't Be Afraid of Types

lmika.org

1–10 of 233 posts

Re: Don't Be Afraid of Types

#2
my code became much easier to maintain once i stopped thinking of it as writing "algorithms" and "processes" and started thinking of it as a series of type conversions.

structuring what lives where became easier, naming things became systematic and consistent, and writing unit tests became simple.

Re: Don't Be Afraid of Types

#3
> I found that there’s a slight aversion to creating new types in the codebases I work in.

I've encountered the same phenomenon, and I too cannot explain why it happens. Some of the highest-value types are the small special-purpose types like the article's "CreateSubscriptionRequest". They make it much easier to test and maintain these kinds of code paths, like API handlers and DAO/ORM methods.

One of the things that Typescript makes easy is that you can declare a type just to describe some values you're working with, independent of where they come from. So there's no need to e.g. implement a new interface when passing in arguments; if the input conforms to the type, then it's accepted by the compiler. I suspect part of the reason for not wanting to introduce a new type in other languages like Java is the extra friction of having to wrap values in a new class that implements the interface. But even in Typescript codebases I see reluctance to declare new types. They're completely free from the caller's perspective, and they help tremendously with preventing bugs and making refactoring easier. Why are so many engineers afraid to use them? Instead the codebase is littered with functions that take six positional arguments of type string and number. It's a recipe for bugs.

Re: Don't Be Afraid of Types

#4
The issue is names.

Every darn little thing in Java needs a name.

If there is no good name, that's a hint that maybe you don't need a new type.

Obligatory Clojure example:

    (defn full-name [{:keys [first-name last-name]}] 
          (str first-name " " last-name))
This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destructuring to access keys `:first-name` and `:last-name` of a map passed in (so the type of the unnamed argument is just Map)

This function works for anything that has a key `:first-name` and `:last-name`.

There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly.

Re: Don't Be Afraid of Types

#5

> I found that there’s a slight aversion to creating new types in the codebases I work in. I've encountered the same phenomenon, and I too cannot explain why it happens. Some of the highest-value types are the small special-purpose types like the article's "CreateSubscriptionRequest". They make it much easier to test and maintain these kinds of code paths, like API handlers and DAO/ORM methods. One of the things that…

> But even in Typescript codebases I see reluctance to declare new types.

The current Typescript hype / trend is to infer types.

Problem is at some point it slow things down to a crawl and it can get really confusing. Instead of having a type mismatch between type A and type B you get an error report that looks like a huge json chain.

Re: Don't Be Afraid of Types

#6
post #4

The issue is names. Every darn little thing in Java needs a name. If there is no good name, that's a hint that maybe you don't need a new type. Obligatory Clojure example: (defn full-name [{:keys [first-name last-name]}] (str first-name " " last-name)) This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destruct…

Did you forget to include `middle-name`?

There's no way to tell.

Re: Don't Be Afraid of Types

#7
post #4

The issue is names. Every darn little thing in Java needs a name. If there is no good name, that's a hint that maybe you don't need a new type. Obligatory Clojure example: (defn full-name [{:keys [first-name last-name]}] (str first-name " " last-name)) This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destruct…

In Python you're describing a Protocol. It's actually super reasonable to have a ObjectWithFirstNameAndLastName noun like this. You don't ever need to construct one but you can use it in the type slot and objects you pass in will be checked to conform. You see all kinds of weird specific types floating around the standard lib like this for type hinting.

Duck typing is great, what's even better is documenting when when they need to quack or waddle.

Re: Don't Be Afraid of Types

#8
Relatedly, don’t be afraid of (database) tables.

It’s okay, you really can have hundreds of tables, your DBMS can handle it.

Obviously don’t create them for their own sake, but there’s no reason to force reuse or generic design for different things.

Re: Don't Be Afraid of Types

#9
post #4

The issue is names. Every darn little thing in Java needs a name. If there is no good name, that's a hint that maybe you don't need a new type. Obligatory Clojure example: (defn full-name [{:keys [first-name last-name]}] (str first-name " " last-name)) This defines a function named `full-name`. The stuff between [] is the argument list. There's a single argument. The argument has no name. Instead it is using destruct…

> Every darn little thing in Java needs a name.

Don't all types need names, regardless of what language you use?

Look at typescript, and how it supports structural typing. They don't seem to have a problem with names. Why do you think Java has that problem when nominal type systems simplify the problem?

> There's no need to declare a type ObjectWithFirstNameAndLastName. It would be quite silly.

Naming things is hard, but don't criticize typing for a problem caused by your lack of imagination. A basic fallback strategy to name specialized types is to add adjectives. Instead of ObjectWithFirstNameAndLastName you could have NamedObject. You don't need to overthink it, with namespaces and local contexts making sure conflicts don't happen.

There are two mindsets: try to work around problems to reach your goals, and try to come up with any problem to find excuses to not reach your goals. Complaining about naming sounds a lot like the second.

Re: Don't Be Afraid of Types

#10

> I found that there’s a slight aversion to creating new types in the codebases I work in. I've encountered the same phenomenon, and I too cannot explain why it happens. Some of the highest-value types are the small special-purpose types like the article's "CreateSubscriptionRequest". They make it much easier to test and maintain these kinds of code paths, like API handlers and DAO/ORM methods. One of the things that…

> I've encountered the same phenomenon, and I too cannot explain why it happens.

I think that some languages lead developers to think of types as architecture components. The cognitive cost and actual development work required to add a type to a project is not the one-liner that we see in TypeScript. As soon as you create a new class, you have a new component that is untested and unproven to work, which then requires developers to add test coverages, which then requires them to add the necessary behavior, etc.

Before you know it, even though you started out by creating a class, you end up with 3 or 4 new files in your project and a PR that spans a dozen source files.

Alternatively, you could instead pass an existing type, or even a primitive type?

> But even in Typescript codebases I see reluctance to declare new types.

Of course. Adding types is not free of cost. You're adding cognitive load to be able to understand what that symbol means and how it can and should be used, not to mention support infrastructure like all the type guards you need to have in place to nudge the compiler to help you write things the right way. Think about it for a second: one of the main uses of types is to prevent developers from misusing specific objects if they don't meet specific requirements. Once you define a type, you need to support the happy flows and also the other flows as well. The bulk of the complexity often lies in the non-happy flows.

Post reply on HN