Hacker News new | ask | show | jobs
by cogman10 1373 days ago
A great idea. Now, everyone that learns this stuff, show some restraint!

The drawback of a powerful type system is you can very easily get yourself into a type complexity mudhole. Nothing worse than trying to call a method where a simple `Foo` object would do but instead you've defined 60 character definition of `Foo` capabilities in the type system in the method signature.

Less is more.

12 comments

My coding philosophy is centered around simple interfaces. I think of power sockets and plugs. The simpler the socket/plug design, the easier it is to plug in. It's easier to connect a European plug which has 2 round pins than it is to connect a UK plug which has 3 rectangular pins at different angles. You can imagine how difficult it would be to connect a plug with 10 pins; it would be difficult to get the alignment right and you would have to push hard and fiddle quite a bit to get it all the way in.

If you can get a module to do the same thing with a simpler interface, then that's generally a better module; it's typically a sign of good separation of concerns. Complex interfaces are often a sign that the module encourages micromanagement of its internal state; a leaky abstraction.

A module should be trusted to do its job. The only reason a module would provide complex interfaces is to provide flexibility... But modules don't need to provide flexibility because the whole point of a module is that it can be easily replaced with other modules when requirements change.

The thing with electrical plus is that they should be designed around safety first, rather than convenience. And the U.K. plug is a lot more safety focused than many other plug standards.

The advantage of the U.K. plug is that live pins are physically blocked and only released when the Earth pin is present. This is why the Earth pin is slightly longer on U.K. plugs and why insulated devices have a plastic Earth pin rather than no pin at all. The advantage of this is so you cannot jam things into them (either accidentally or intentionally) without the Earth pin. Thus making the plug much safer.

I’ve found U.K. plugs to be much more secure inside the socket too. US plugs often come away from the wall when there is a little bit of weight or tension on the plug. U.K. plugs require a great deal more pressure to come loose from the socket.

If I were to bring this back to types I’d say one needs to evaluate what the requirements are: safety or convenience.

When I was a kid before we had legos we had some soviet alternative called constructor or something. Everything was made from metal, crews etc. obviously as a kid one of the first “hello world” things you will build is “plug” that you can insert into those holes in the wall. My older brother was lucky when he did it as the fuse in the house went off. My younger brother did the same about a decade later but holding in his hand two metal pins. He was also lucky that my dad was just passing through corridor and pushed him out. He got away with just burned skin on the fingers and a bit of shock.
Sounds like a more dangerous version of meccano
Regular Meccano could played with this way too. Albeit I don't know if the pieces are shaped right to fit into a plug socket. But I'd wager that's more by accident than design
And now try the Schuko, which improves on all metrics you named (except the polarity isn't fixed, that's its one theoretical disadvantage), but also significantly improves usability (you can plug it in either way, the plug goes in much easier, and it stays in with much more force)
It’s a good design but I disagree that it improves on all the safety features. For starts the child proof safety shutters are still only optional in some regions.

> except the polarity isn't fixed, that's its one theoretical disadvantage

Polarity isn’t fixed on any mains sockets. That’s why the A in AC stands for “alternating”

And yet there is a big difference between the hot and neutral conductors in a 120V outlet in the US. The neutral remains close to ground potential, and is actually bonded to earth at the breaker panel.
I’ll admit that my understanding of these things is rather superficial. I might understand more than the average person but that’s not exactly a high bar to set.

However I always understood potential to be different to polarity. And that AC (which, to my knowledge, all electric grids globally carry) is the literal oscillation of polarity. What am I missing/misunderstanding from the GPs post?

I don't think the discussion really was about plugs.
Originally, no. But discussions evolved
It's true that EU plugs of some devices can be a bit loose; especially 2-pin variants where the wall socket is not indented or the indent is taller than the plug.
USB-C would be a nice looking single-argument function... but parameterized with every possible generic template meta-programming feature in the language.
I love the socket/plug analogy.

It's even better than it looks in Europe: there are actuality 3 contact points but only 2 are salient which allows for 2 easily pluggable positions (rotate 180deg). The ground is positioned twice for that matter.

Only France has a variation around that to my knowledge, that is still compatible across Europe.

And no one notices and just plugs in and out without thinking twice about it.

There are some unsung heroes here.

The EU has a huge variety of plugs and sockets, what people often call the EU plug are variation of the schuko[0] design. The standard EU plug is the Europlug[1] which is compatible with most (but not all) sockets in Europe and only handles low amperage.

AFAIK Italy is the major outlier in having widespread sockets[2] that do not accept the Europlug

[0] https://en.wikipedia.org/wiki/Schuko

[1] https://en.wikipedia.org/wiki/Europlug

[2] https://en.wikipedia.org/wiki/AC_power_plugs_and_sockets#Ita... the one on the left, rated for 16A.

A tiny bit more complex: the German/European plug has the ground at the sides and so goes in both ways. The French/Belgian plug has instead a ground pin that goes from the socket into the plug (so the opposite of the other two). That means generally you'll be able to combine both and most products have a plug that allows to connect to both varieties. But there are some (especially Chinese and American) products which miss this diversity and either only fit in one of the two or produce local varieties for either one when it would well be feasible to produce a single version that fits both.
> It's easier to connect a European plug which has 2 round pins than it is to connect a UK plug which has 3 rectangular pins at different angles.

Speaking strictly on plugs - while I agree it is simpler/easier - I disagree that EU plug is better.

For the bulk and lack of directionality - significantly safer (pin lengths, shielded to tip), requires a ground pin even if dummy, carries 13A easy!

The safety aspects are incredible[0]. I do not worry about my small children at all.

Lived in HK where the EU (China) and UK (HK) are pretty common in one household.

[0] https://www.youtube.com/watch?v=UEfP1OKKz_Q

This is so true. I've been thinking recently that in the same way that "use boring technology" is a pretty well-known concept, so should "use boring types" enter the collective conscious. Type operators are exciting and flashy, but I found that using them too much leads to brittle and confusing types. Saving them for a last resort tends to be the right strategy. Often there's an extremely dumb way to write your types that works just as well - maybe even better :)
Agreed! Boring interface definitions is my rule for typing.

I see way too many folks trying to use Omit<>(…) and Partial<>(…) creating absolute typing monstrosities. Feels like typing duct tape and it’s impossible to read the type definition when it’s generated in a tooltip.

Although to be fair, lack of visibility in the tooltip seems like a problem with the tools not with types. Many times I’ve wished I could tell TS to expand the next level of type signature.
If you only use "simple types" then you often get implicit assumptions about the "values". For instance note that "web-services" basically means you have a very simple interface defined by the HTTP-protocol.

But what is actually inside the HTTP-payloads can then have many constraints on them which are not declared anywhere. For instance your code might assume the payload is JSON with several required fields in it.

For that you have the "closed for modification but open for extension" principle.

If the server is new and fresh, yeah it's ok to assume the payload of a request will be a JSON object with some required fields, but leave the parameter there in case someone decides they will start sending XML payloads to it

"Duplication is better than the wrong abstraction"

This is where a lot of developers go overboard - not just in type systems, but in general. They are so afraid of duplication, they over-generalize and end up in a quagmire of unreadable overly complicated code.

Some duplication is easy. It's just code volume, and volume shouldn't be as scary as complexity.

The focus should be readability of code while respecting abstractions and design patterns.

I prefer some duplicate lines over having to go back and forth over some source files only because some developers think that less code is better code.

The code we create should be made for humans to read, no to machines and specially not to brag about how clever is our code

Second this. By the end of a progressive multi-year TS migration at my last company, we were refactoring `HTTPRequest<GetRequestBody<IncrementUpdate>>` back into its JS ancestor `HTTPGetRequest`.
True that.

Once types get so complex, I’ve no idea what’s going wrong.

Today I had code running fine but throwing errors all over the place because some deeply nested type mismatch between two libraries.

I just any’d it… i aint got no time for that shit

But would the code really work without the type checking?
Are types always correct?
No
Yes. I had a huge testing suite on the project and all tests were green even though there were tons of type errors.
You maybe just lack experience with more complex types. Which is totally expected, since you probably learned programming the runtime all the time, but not the typesystem - unless you come from one of those rare languages that have a similar powerful typesystem.

But just like you probably struggled with and overcame many things before, it will be the same now. It's just that you can opt out of the typesystem in typescript whereas you are forced to learn how to deal with the runtime.

But if you make it, your development experience will change drastically. The time might be very well spent.

I’ve used C++, C# and Java. Do these count as languages with complex types? I also used typescript a lot and have plenty of more complex types using generics and dynamic generation of types based on other types.

The problem with typescript is that the types are very often wrong or needlessly complicated.

Having separate type definitions from the library is stupid. Having types missing from @types/node is stupid. Many libraries lie about their types.

You can act like typescript is some kind of magic miracle that will save you time, but in reality it is just riddled with many small time-consuming stupidities. Typescript is just trying to a polished turd. Shiny on the outside, but shit within, and this is by design because it needs to work with JS and other poorly typed libraries and code.

If I haven’t convinced you TS is a polished turd, just wait until you find out how to import a modern nodejs module in a typescript project. Hint: you have to import a .js file, even though your file is called .ts.

The tsconfig has so many options that every project is different and a lot of code isn’t interchangeable and can break if copied from one project to another.

Want to convert some TS code to JS without checking types? No can do! Ts-node can do it, but tsc cannot.

None of these count as a language with complex types in my opinion. C++ has templates but that's not to be confused with its typesystem.

I'm not saying typescripts typesystem is perfect and I'm definitely not saying that most people use it correctly. But at least it has great potential, compared to e.g. Java and C# which still fail to let me describe basic data types and operations in the typesystem.

No need to show any restraint. It's much more fun to explore and burn yourself once into understanding how much of this power your need. Or twice. Or as many times until it gets more fun to be pragmatic.
Ah, the "smoke a whole pack of cigarettes" method of teaching software dev.

I kinda agree - often no amount of "this is a bad idea" will teach as well as just letting someone make the mistake and actually experience the consequences.

The only problem is that hard to maintain code often does not cause any problems until you write a critical mass of it and end up trying to develop enough non trivial extra features on top of it.

It's more fun, but your fun shouldn't come in the way of getting work done - or worse, getting in the way of others getting their work done.
I wouldn't say it's a matter of fun but often a matter of necessity in modern software development. Less experienced people can often have inflated egos and will refuse to listen to any advice from anyone else. Letting them fuck something up themselves (not too badly) after you've explained why it's a bad idea can be a hard but good lesson.
The types in your code are just as designed just like any other aspect of it. It's not a matter of restraint, it's a matter of doing things on the correct way.
I don't believe in "correct" when it comes to software dev. There's 1000 solutions to any given problem.

It's a matter of choosing a solution that is clear, easy to understand, and easy to maintain. There are nearly limitless solutions that can fit that definition.

Restraint comes into play because devs tend to "treat every problem like a nail when they have a hammer". When devs learn new concepts, they often look for places to use that concept even when it's a bad fit.

An example of this is excessive use of inheritance when simpler types fit better. Many of us have dealt with the greenhorn that creates a giant inheritance tree or generic mess after they first learn that "neat" concept.

So less can be more but more can also be more, more or less?
Exactly. "More" and "less" are pretty much wrong ways to measure it.
I definitely know what you mean. There is usually a type that is "just right" in terms of rigidity and flexibility.

The reasons OP encourage restraint might be the mental overhead of understanding what's "correct", as well as needing to rely on not only yourself but other people to be correct.

Sometimes simple is faster and harder to screw up.

What's 'correct' from your point of view (as library implementer) may be 'overly complex and a hassle to use' from the library user's pov though.
Over the years I've actually found duck typed code bases to end up, for lack of a better word, less of a mess than those based on typed languages.

I feel like readable dynamically typed code is more easily "trained" onto younglings than typed equivalent.

I understand no typings allow for much much worse code bases, but my experience has been the opposite.

Less is more is also important for writing performant code. JS engines care about types, in the form of ‘shapes’ which is the V8 term for a specific structural layout and maps pretty neatly to TS structural types for obvious reasons. Simple types make performance issues like megamorphic inline cache issues much harder to create. If you see type spaghetti it’s a good hint that performance issues may be lurking depending on actual runtime usage. And if your runtime usage is actually simple then you don’t particularly need the flexibility the type spaghetti provides.
Also typescript doesn't always infer things thoroughly with generics and deeply nested types, so I’ve ended up avoiding them when possible. Performance also explodes if you have too much unions and stuff. Beware! Definitely some popular libraries out there that went overboard and nuke compiler performance for minimal gain.
I just finished writing a mess of very complicated types that can parse an open API spec, provide the request types (body & params) as input, and restrict the return to the appropriate response type... I hooked this higher order function into our API and immediately found 30-40 different places where the implementation was not aligned with the spec.

The devs now have guardrails in place to make sure they follow the spec...

Advanced types are invaluable when you are writing a framework or library... But in every day implementation, I agree they should be used sparingly.

I have 7 years of ts experience and I'll still 'as any' a reduce function from time to time

If your `Foo` object has 60 potential character definitions... that sounds (smells) like it would be a code smell of something wrong upstream. Perhaps it's time to refactor the function.

Ignoring the 60 different character definitions isn't going to make the problem that you have 60 possible variants go away just because you didn't type it.