Don't Be Afraid of Types

lmika.org

132 points by speckx a month ago


DeathArrow - a month ago

People might be afraid of types because in OOP land there's the idea that types aren't mere containers for data.

You have to use encapsulation, inheritance and polymorphism. Fields and properties shouldn't have public setters. You assign a value only through a method, otherwise you make the gods angry.

You have gazillions of constructors, static, public, protected, private and internal. And you have gazillions of methods, static, public, private, protected and internal.

You inherit at least an abstract class and an interface.

You have at least some other types composed in your type.

Unless you do all this, some people might not consider them proper types.

I had my engineering manager warn me that data classes are "anemic models". Yes, but why? "We should have logic and methods to set fields in classes". "Yes, but why?" "It's OOP and we do DDD and use encapsulation." "Yes, but why? Imagine we have immutable records that hold just data and static classes as function containers, and those functions just act on the records, return some new ones and change no state. This way we can reason with ease about our goddam software, especially if we don't encapsulate state and mutate it all over the place, Uncle Bob, be damned." He shook his head in horror. He probably thinks I am a kind of heretic.

beders - a month ago

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.

darioush - a month ago

Kind of disagree with this article, when you add a "noun" (aka type), you're often introducing a new abstraction.

Abstractions have a maintenance cost associated with it ie, another developer or possibly yourself must be able to recreate the "algebra" associated with that type (your thought process) at the time of making modifications. This creates some problems:

1. Since there's no requirement to create a cohesive algebra (API), there was probably never a cohesive abstraction to begin with.

2. Requirements may have changed since the inception of the abstraction, further breaking its cohesion.

3. Since we largely practice "PR (aka change) driven development", after a few substantial repetitions of step 2, now the abstraction has morphed into something that's actually very tied into the callsites (verbs), and is essentially now tech debt (more like a bespoke rube goldberg machine than a well-designed re-usable software component).

You can introduce types if you follow the open/closed principle which means you don't change abstractions after their creation (instead create new ones and then delete old ones when they have no callsites).

parpfish - a month ago

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.

jt2190 - a month ago

If you ignore the weird rant about OOP (that references an article from 2006… haven’t we all moved on in the last twenty years?), the author’s main thesis lacks context:

> But take it from someone that’s had do deal with codes passing through and returning several values of strings, ints, and bools through a series of function calls: a single struct value is much easier to work with.

This presupposes that the code should be very strict with the data, which may or may not be desirable. For example, in many CRUD apps the client and the database enforce constraints, while the middle tier just needs to martial data between the two, and it’s questionable whether the middle tier should do type enforcement. As always: Challenge your assumptions, try to understand how a “bad” practice might actually be the right approach in certain contexts, etc, etc.

karparov - a month ago

> That’s what the type system is for: a means of grouping similar bits of information into an easy-to-use whole.

While types can be used for that, they are a much broader concept.

I would say the general purpose of types is to tell apples from oranges.

paulddraper - a month ago

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.

salmonellaeater - a month ago

> 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.

salgernon - a month ago

I was refactoring a 700 line recursive C function (!) - one of those with all variables declared at the outer scope while the function itself was mainly a one pass switch with goto’s for error handling. I created c++ classes for each case, hoisted them out and coalesced types that were otherwise identical. The new version was way smaller and and (imho) far more readable and maintainable.

At some point I needed to change the types to capture a byte range from a buffer rather than just referring to the base+offset and length, and it was trivial to make that change and have it “just work”.

These were no vtable classes with inline methods, within a single compilation unit - so they just poof go away in a stripped binary.

‘Tis better to create a class, than never to have class at all. Or curse the darkness.

jongjong - a month ago

I'm not a huge fan of types because they don't model the real world accurately. In the real world, most concepts which we describe with human language have many variations with optional properties and it's a pain and a waste of time to try to come up with labels to categorize each one... It induces people to believe that two similar concepts are distinct, when in fact, they are extremely similar and should share the same name. I hate codebases which have many different types for each concept... Just because they have a few properties that are different. It overcomplicates things and creates logical boundaries in the system before they are needed or well defined. It forces people to categorize concepts before they understand the business domain. People will argue that you can define types with optional properties, that's a fair point but you lose some type safety if you do that... If you can do away with some type safety (with regard to some properties), surely you can do away with it completely?

boris_m - a month ago

Languages like Java are awful in that respect, as they make it super hard to declare new types.

People expect for a type to contain some logic, but it doesn't have to. e.g. a configuration is a type that contains other types that contains yet other types. But I have never seen it done like that in languages like Java.

enriquto - a month ago

As somebody who is afraid of types (and also, who hates types, because we all hate what we fear), may my point of view serve as balance: you don't need a type system if everything is of the same type. Programming in a type-less style is an exhilarating and liberating experience:

assembler : everything is a word

C : everything is an array of bytes

fortran/APL/matlab/octave : everything is a multi-dimensional array of floats

lua : everything is a table

tcl : everything is a string

unix : everything is a file

In some of these languages there are other types, OK, but it helps to treat these objects as awkward deviations from the appropriate thing, and to feel a bit guilty when you use them (e.g., strings in fortran).

- a month ago
[deleted]
teeray - a month ago

My favorite is when I come across a set of functions in Go with the same prefix that all take some struct treated as a bag of data. “validateUserRequest”, “processUserRequest”, “initializeUserRequest”, or something like that. You have written a type already, but made it annoying. Write methods for “Validate” and “Process”, then have a factory function “NewUserRequest” (consider whether you actually need it first though).

joaonmatos - a month ago

I think we're moving past this fear, thankfully. When you look at newer Java features (records, destructuring, value types) you see that even in the most stereotypical OOP language we are developing a language to describe both behavior (what we always had, with SOLID and domain classes and so on), but also a lightweight language to operate on pure data.

galaxyLogic - a month ago

Types are the dual of Functions. You could create very many functions which differ just a bit, or you could create functions which take arguments/parameters and do with a smaller set of functions.

So how about using generic (=parameterized) types? Isn't that the answer?

ninetyninenine - a month ago

In Java you can’t just create a type. You are creating a mini app.

Classes in Java hold logic and internal mutating state. I don’t want to create this just because I need a type.

Really you want to create a struct or an interface as a type.

legulere - a month ago

Also known as primitive obsession

noduerme - a month ago

Huh. I don't see any conflict between loving OO-programming and also loving types. Isn't 9/10ths of OO just about consolidating your business logic into interfaces and types that you'd ideally want to re-use? I feel like if it's not, then people are using OO the wrong way.

pcwelder - a month ago

Encapsulation isn't the real reason why types help. Pass 5 variables individually, the only difference will be in verbosity.

The real benefit is type narrowing. (Or declaring the space of all possible combinations of values.)

Instead of

id: null | string

name: null | string

...

You'd have

user: null | User

And

User: { id: string, name: string, ..}

declaring that all are not null together.

Pushing validation to static checker instead of runtime.

necovek - a month ago

Please be afraid of types.

On top of new cognitive load, introduction of redirection, and anti-patterns like pretend-simplification where you shuffle a bunch of unrelated parameters into a struct to make a function receive only a single argument (which quickly evolves into functions receiving parts of that struct they don't need, making use and testing much harder — I am surprised an article is really recommending this as a strategy), your types frequently get exposed to external users, and you need to start thinking about both backwards- and forwards-compatibility.

Benefits of types should be carefully balanced against the cost of introducing them, and while calling that "fear" might not be appropriate, it should be a conscious cost-benefit analysis.