Live data from Hacker News

Use Your Type System

dzombak.com

281–290 of 357 posts

Re: Use Your Type System

#281

Shoutout to the absolutely awesome beartype for python. With simple decorators it adds runtime type checking with virtually no performance cost!! https://beartype.readthedocs.io/en/latest/ Or see their page about performance: https://beartype.readthedocs.io/en/latest/faq/#faq-realtime

I don't get it. If you already have type annotations just run Pyright surely?

Re: Use Your Type System

#282
post #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 const…

Yeah I agree with wvenable. It's definitely safer but most languages have very poor support for doing it ergonomically. In fact I have yet to find one that makes it genuinely nice.

Re: Use Your Type System

#283

Shoutout to the absolutely awesome beartype for python. With simple decorators it adds runtime type checking with virtually no performance cost!! https://beartype.readthedocs.io/en/latest/ Or see their page about performance: https://beartype.readthedocs.io/en/latest/faq/#faq-realtime

I don't get it. If you already have type annotations just run Pyright surely?

No, pyright is called on static code. Beartime is called during the python process. Basically all my functions are decorated.

See for example in wdoc, my advanced personal RAG system:

https://github.com/thiswillbeyourgithub/wdoc/blob/main/wdoc/...

Re: Use Your Type System

#284
post #15

Hard not to agree with the general idea. But also hard to ignore all of the terrible experiences I've had with systems where everything was a unique type. In general, I think this largely falls when you have code that wants to just move bytes around intermixed with code that wants to do some fairly domain specific calculations. I don't have a better way of phrasing that, at the moment. :(

Maybe I know what you mean. There are cases where you have the data in hand but now you have to look for how to create or instantiate the types before you can do anything with it, and it can feel like a scavenger hunt in the docs unless there's a cookbook/cheatsheet section. One example is where you might have to use createVector(x, y, z): Vector when you already have { x, y, z }. And only then can you createFace(ver…

This stuff gets unbearable very fast. We have custom types for geometries at my work. We also use a bunch of JS libraries for e.g. coordinate conversions. They output as [number, number, number], whereas our internal type are number[].

Re: Use Your Type System

#285

Shoutout to the absolutely awesome beartype for python. With simple decorators it adds runtime type checking with virtually no performance cost!! https://beartype.readthedocs.io/en/latest/ Or see their page about performance: https://beartype.readthedocs.io/en/latest/faq/#faq-realtime

you never got a TypeError before? Python already has runtime type checking

If you have a TypeError it's already too late. Decorating everything with beartype means I can catch things way in advance. It also forces me to specify types to all my functions which really helps LLM.

See for example in wdoc, my advanced personal RAG library:

https://github.com/thiswillbeyourgithub/wdoc/blob/main/wdoc/...

Re: Use Your Type System

#286
post #15

Hard not to agree with the general idea. But also hard to ignore all of the terrible experiences I've had with systems where everything was a unique type. In general, I think this largely falls when you have code that wants to just move bytes around intermixed with code that wants to do some fairly domain specific calculations. I don't have a better way of phrasing that, at the moment. :(

The article is written in Go, in which - iirc - it's fairly easy and cheap to convert a type alias back to its original type (e.g. an AccountID to an int).

Using the right architecture, you could make it so your core domain type and logic uses the strictly typed aliases, and so that a library that doesn't care about domain specific stuff converts them to their higher (lower?) type and works with that. Clean architecture style.

Unfortunately, that involves a lot of conversion code.

Re: Use Your Type System

#287
post #110

The equivalent in Python is:- from typing import NewType UserId = NewType("UserId", int)

Actually, not really. In this case UserId is still an integer, which means any method that takes an integer can also take a UserId. Which means your co-workers are likely to just use integer out of habit. Also, you can still do integer things with them, such as > nonsense = UserId(1) + UserId(2)

It will not stop you doing this at runtime of course. It is only intended for type checking.

Re: Use Your Type System

#288
post #67
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.

It's taking the original idea behind Hungarian notation (now called "Apps Hungarian notation" to distinguish from "Systems Hungarian notation" which uses datatype) and moving it into the type system. To keep building on history, I'd suggest Hungarian types.

They were already called "painted types" by same the inventor of Hungarian notation. Simonyi used this term in his PhD thesis, Meta-programming, in 1976! (Published 1977).

Meta-programming also introduced a notation which was the precursor to Hungarian Notation (page 44,45), so painted types technically pre-date Hungarian Notation.

https://web.archive.org/web/20170313211616/http://www.parc.c...

Relevant quote:

> These examples show that the idea of types is independent of how the objects belonging to the type are represented. All scalar quantities appearing above - column numbers, indices and so forth, could be represented as integers, yet the set of operations defined for them, and therefore their types, are different. We shall denote the assignment of objects to types, independent of their representations, by the term painting. When an object is painted, it acquires a distinguishing mark (or color) without changing its underlying representation. A painted type is a class of values from an underlying type, collectively painted a unique color. Operations on the underlying type are available for use on painted types as the operations are actually performed on the underlying representation; however, some operations may not make sense within the semantics of the painted type or may not be needed. The purpose of painting a type is to symbolize the association of the values belonging to the type with a certain set of operations and the abstract objects represented by them.

Re: Use Your Type System

#289

Type systems, like any other tool in the toolbox, have an 80/20 rule associated with them. It is quite easy to overdo types and make working with a library extremely burdensome for little to no to negative benefit. I know what a UUID (or a String) is. I don't know what an AccountID, UserID, etc. is. Now I need to know what those are (and how to make them, etc. as well) to use your software. Maybe an elaborate type sy…

> I know what a UUID (or a String) is. I don't know what an AccountID, UserID, etc. is.

It's literally the opposite. A string is just a bag of bytes you know nothing about. An AccountID is probably... wait for it... an ID of an Account. If you have the need to actually know the underlying representation you are free to check the definition of the type, but you shouldn't need to know that in 99% of contexts you'll want to use an AccountID in.

> Now I need to know what those are (and how to make them, etc. as well) to use your software.

You need to know what all the types are no matter what. It's just easier when they're named something specific instead of "a bag of bytes".

> https://grugbrain.dev/#grug-on-type-systems

Linking to that masterpiece is borderline insulting. Such a basic and easy to understand usage of the type system is precisely what the grug brain would advocate for.

Re: Use Your Type System

#290
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…

As you mentioned correctly: if you go for strongly typed types in a library you should go all the way. And that means your strong type should provide clear functions for its conversion to certain other types. Some of which you nearly always need like conversion to a string or representation as a float/int.

The danger of that is of course that you provide a ladder over the wall you just built and instead of

   temperature_in_f = temperature_in_c.to_fahrenheit()
They now go the shortcut route via numeric representation and may forget the conversion factor. In that case I'd argue it is best to always represent temperature as one unit (Kelvin or Celsius, depending on the math you need to do with it) and then just add a .display(Unit:: Fahrenheit) method that returns a string. If you really want to convert to TemperatureF for a calculation you would have to use a dedicated method that converts from one type to another.

The unit thing is of course an example, for this finished libraries like pythons pint (https://pint.readthedocs.io/en/stable/) exist.

One thing to consider as well is that you can mix up absolute values ("it is 28°C outside") and temperature deltas ("this is 2°C warmer than the last measurement"). If you're controlling high energy heaters mixing those up can ruin your day, which is why you could use different types for absolutes and deltas (or a flag within one type). Datetime libraries often do that as well (in python for example you have datetime for absolute and timedelta for relative time)

Post reply on HN