Hacker News new | ask | show | jobs
by xboxnolifes 867 days ago
Crazy that actually using your type system leads to better code. Stop passing everything around as `string`. Parse them, and type them.
3 comments

There's a name for this anti-pattern: "Stringly typed"
I've also seen it called primitive obsession, which is also applicable to other primitive types like using an integer in situations where an enum would be better.
Definitely use to fall for primitive obsession. It seemed so silly to wrap objects in an intermediary type.

After playing with Rust, I changed my tune. The type system just forces you into the correct path, that a lot of code became boring because you no longer had to second guess what-if scenarios.

> Definitely use to fall for primitive obsession. It seemed so silly to wrap objects in an intermediary type.

A lot of languages certainly don't make it easy. You shouldn't have to make a Username struct/class with a string field to have a typed username. You should be able to declare a type Username which is just a string under the hood, but with different associated functions.

We use this pattern extensively in a large Java app. As long as you establish these patterns early on in the project, the team adapts to the conventions. It's worked well for us and the lack of language support doesn't get in the way much.
Yeah, modern type systems are game changers. I've soured on Rust, but if Go had the full Ocaml type system with match statements I think it would be the perfect language.
Go would need such a revamp to be anywhere close to a decent language, that it would be just a straight up other language.
Sadly enums are too advanced of a concept to be included in Go.
This term is typically used to refer to things like data structures and numerical values all being passed as strings. I don't think a reasonable person would consider storing a username in a string to be "stringly typed".
It definitely is stringly typed. It's just that it's a very normalized example of it, that people don't think of as being an antipattern.

If you want to implement what Yaron Minsky described as "make illegal states unrepresentable", then you use a username type, not a string. That rules out multiple entire classes of illegal states.

If you do that, then when you compile your program, the typechecker can provide a much stronger correctness proof, for more properties. It allows you to do "static debugging" effectively, where you debug your code before it ever even runs.

I don’t get what you’re about. The root comment clearly presents a structure of a separate type. The fact that it happens to contain a single string field is completely irrelevant (what type an actual username should be, a float?). “Stringly typed” is about stringifying non-string values to save typing work and is not applicable here in the slightest.
I wasn't replying to the root comment, I was replying in the context of the subsequent three comments, specifically:

> > > Crazy that actually using your type system leads to better code.

> > There's a name for this anti-pattern: "Stringly typed"

> I don't think a reasonable person would consider storing a username in a string to be "stringly typed".

#1 was saying that the root comment shows better code using the type system.

#2 was clearly referring to the case where you don't do this as being an anti-pattern.

#3 is saying that storing a username in a string, without wrapping defining a distinct type for it, was not stringly typed. But as I pointed out, it certainly is.

If you doubt my interpretation of #3, the same commenter said this in another comment: "Is it really more 'programmer friendly' to create wrapper types for individual strings all over your codebase?"

I see, my apologies!
I wasn’t sure who was right. I’ll tie break with https://wiki.c2.com/?StringlyTyped= which pretty much says what you just said
The commenter you're replying to misunderstood the discussion. See my sibling reply.
The One True Wiki[0] says "Used to describe an implementation that needlessly relies on strings when programmer & refactor friendly options are available."

Which is exactly what's going on here. A username has a string as a payload, but that payload has restrictions (not every string will do) and methods which expect a username should get a username, not any old string.

[0]: https://wiki.c2.com/?StringlyTyped

I don't agree that this example is more "programmer friendly". Anything you want to do with the username other than null check and passing an argument is going to be based directly on the string representation. Insert into a database? String. Display in a UI? String. Compare? String comparison. Sort? String sort. Is it really more "programmer friendly" to create wrapper types for individual strings all over your codebase that need to have passthrough methods for all the common string methods? One could argue that it's worth the tradeoff but this C2 definition is far from helpful in setting a clear boundary.

Meanwhile the real world usages of this term I've seen in the past have all been things like enums as strings, lists as strings, numbers as strings, etc... Not arbitrary textual inputs from the user.

You inherit some code. Is that string a username or a phone number? Who knows. Someone accidentally swapped two parameter values. Now the phone number is a username and you’ve got a headache of trying to figure out what’s wrong.

By having stronger types this won’t come up as a problem. You don’t have to rely on having the best programmers in the world that never make mistakes (tm) to be on your team and instead rely on the computer making guard rails for you so you can’t screw up minor things like that.

I agree on the one hand but empirically I don’t think I have seen a bug where the problem was the string for X ended up being used as Y. Probably because the variable/field names do enough heavy lifting. But if your language makes it easy to wrap I say why not. It might aid readability and maybe avoid a bug.

I would probably type to the level of Url, Email, Name but not PersonProfileTwitterLink.

> Is it really more "programmer friendly" to create wrapper types for individual strings all over your codebase that need to have passthrough methods for all the common string methods?

That can be handled transparently in languages that have good support for strong type systems, like Rust or Haskell, using traits or type classes.

What you're saying is essentially that addressing stringly typing can only be taken so far in weakly typed languages, without becoming inconvenient.

> Meanwhile the real world usages of this term I've seen in the past have all been things like enums as strings, lists as strings, numbers as strings, etc... Not arbitrary textual inputs from the user.

The definitional question is not that interesting. The point is that the concept applies just as much to a username represented as a string as it does to any other kind of value being represented as a string.

The reason is simple, which is just that "string" is a general type that can represent anything, whereas "username" is a subset of all possible strings. If you're trying to use your type system to ensure correct code, you want to be able to type check a function signature like `f(user, company, motto)`, just to take a simple example.

Bash :(
JSON
TCL
As a PHP developer I am frankly disappointed you think that we only do that with strings. I've got an array[1] full of other tools.

1. Or maybe a map? Those keys might have significance I didn't tell you about.

I originally typed out `int` and wanted to do more, but I try to keep my comments as targeted as possible to avoid the common reply pattern of derailing a topic by commenting on the smallest and least important part of it. If I type `string`, `int`, `arrays`, `maps`, `enums`... someone will write 3 paragraphs about enums are actually an adequate usage of the type system, and everyone will focus on that instead of the overarching message.
things have different costs.

Types limit you from making some mistakes, but it also impacts your extensibility. Imagine an enum with 4 values and you want to add 1 because 10 level deep one of the services need new value. How does it usually go with strongly typed languages? You go and update all services until new value is properly propagated to lowest level who actually needs that value.

Now imagine doing same with strings, you can validate at the lowest level, upper levels just pass value as it is. If upper layers have conditionals based on value, they still can limit their logic to those values

Why would you need to update code that isn't matching on the value? It just knows it has an X and passes it to a function that needs an X.
if you don't update the code in intermediate layers, some automated validation based on enum values will fail, which also drops the request
You only need to update the parser and the places that are using it. Depending on language, the parser might update itself (Scala generally works this way). Everyone else has an already parsed value that they're just passing around. That's the point: only run your validation at the outer layer of your application.