Live data from Hacker News

Use Your Type System

dzombak.com

221–230 of 357 posts

Re: Use Your Type System

#221

Earlier quoted context omitted.

And that's my point: I'm usually getting AccountIDs from strings (passed in via HTTP requests) so the whole thing becomes a pointless exercise.

Do you validate them? I assume you do. Feels like a great time to cast them too

'Parse, Don't Validate'

Re: Use Your Type System

#222

This reminds me of the mp-units [1] library which aims to solve this problem focusing on the physical quantities. The use of strong quantities means that you can have both safety and complex conversion logic handled automatically, while having generic code not tied to single set of units. I have tried to bring that to the prolog world [2] but I don't think my fellow prolog programmers are very receptive to the idea ^…

I remember a long, long time ago, working on a project that handled lots of different types of physical quantities: distance, speed, temperature, pressure, area, volume, and so on. But they were all just passed around as "float" so you'd every so often run into bugs where a distance was passed where a speed was expected, and it would compile fine but have subtle or obvious runtime defects. Or the API required speed i…

See https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...

Re: Use Your Type System

#223
post #219

I've used the approach described for uuids on a project and I liked it. We were using typescript so we went further using template literal types [1] type UserId = `user:${uuid}`; type OrgId = `org:${uuid}`; This had the benefit that we could add validation (basic begins with kind of logic) and it was obvious upon visual inspection (e.g. in logs/debugging). 1. https://www.typescriptlang.org/docs/handbook/2/template-li…

I assume you used these against a relational database? Did you commit those ids with the prefix still attached? or did you `.split()[1]` or something? I think it's a pretty good idea. I'm just wondering how this translated to other systems.

We were using Mongo and stored the ids with the prefix in the DB as the primary key. Pretty much everywhere we were passing them around as strings, never as 128 bit int, so there was no integrity checking outside of the app layer.

The only drawback was marshalling the types when they come out of the db layer. Since the db library types were string we had to hard cast them to the correct types, really my only pain. That isn't such a big deal, but it means some object creation and memory waste, like:

    // pseudo code:
    const results = dbclient.getObjectsByFilter( ... );
    return results.map(result => ({
        id: result.id as ObjectId,
        ...
    }));
We normally didn't do it, but it would be at that time you could have some `function isObjectId(id:string) : id is ObjectId { id.beginsWith("object:"); }` wrapper for formal verification (and maybe throw exceptions on bad keys). And we were probably doing some type conversions anyway (e.g. `new Date(result.createdAt)`).

If we were reading stuff from the client or network, we would often do the verification step with proper error handling.

Re: Use Your Type System

#224
post #37

An adjacent point is to use checked exceptions and to handle them appropriate to their type. I don't get why Java checked exceptions were so maligned. They saved me so many headaches on a project where I forced their use as I was the tech lead for it. Everyone hated me for a while because it forced them to deal with more than just the happy path but they loved it once they got in the rhythm of thinking about all the…

C# went with properly typed but unchecked exceptions. IMO it gives you a clean error stacks without too much of an issue.

I also think its a bit cleaner to have a nicely pattern matched handler blocks than bespoke handling at every level. That said, if unwrapped error results have a robust layout then its probably pretty equivalent.

Re: Use Your Type System

#225
post #25

Earlier quoted context omitted.

Yep. For this reason, I wish more languages supported bound integers. Eg, rather than saying x: u32, I want to be able to use the type system to constrain x to the range of [0, 10). This would allow for some nice properties. It would also enable a bunch of small optimisations in our languages that we can't have today. Eg, I could make an integer that must fall within my array bounds. Then I don't need to do bounds ch…

You can do this quite easily in Rust. But you have to overload operators to make your type make sense. That's also possible, you just need to define what type you get after dividing your type by a regular number and vice versa a regular number by your type. Or what should happen if when adding two of your types the sum is higher than the maximum value. This is quite verbose. Which can be done with generics or macros.

You can do it at runtime quite easily in rust. But the rust compiler doesn’t understand what you’re doing - so it can’t make use of that information for peephole optimisations or to elide array bounds checks when using your custom type. And you don’t get runtime errors instead of compile time errors if you try to assign the wrong value into your type.

Re: Use Your Type System

#226
post #39

In C#, I often use a type like: readonly struct Id32 { public readonly int Value { get; } } Then you can do: public sealed class MFoo { } public sealed class MBar { } And: Id32 x; Id32 y; This gives you integer ids that can’t be confused with each other. It can be extended to IdGuid and IdString and supports new unique use cases simply by creating new M-prefixed “marker” types which is done in a single line. I’ve als…

This technique is called 'phantom type' because no values of MFoo or MBar exist at runtime.

Re: Use Your Type System

#227
post #25

Earlier quoted context omitted.

Yep. For this reason, I wish more languages supported bound integers. Eg, rather than saying x: u32, I want to be able to use the type system to constrain x to the range of [0, 10). This would allow for some nice properties. It would also enable a bunch of small optimisations in our languages that we can't have today. Eg, I could make an integer that must fall within my array bounds. Then I don't need to do bounds ch…

in raku, that’s spelled subset OneToTen of Int where 1..10:

Off topic: do you use raku in day to day life? I tried learning it but perl5 remains my go-to when I just need to whip something up

Re: Use Your Type System

#229

Earlier quoted context omitted.

The full-blown version that guarantees no bounds-check errors at runtime requires dependent types (and consequently requires programmers to work with a proof assistant, which is why it's not very popular). You could have a more lightweight version that instead just crashes the program at runtime if an out-of-range assignment is attempted, and optionally requires such fallible assignments to be marked as such in the c…

AIUI WUFFS doesn't need a full blown proof assistant because instead of attempting the difficult problem "Can we prove this code is safe?" it has the programmer provide elements of such a proof as they write their program so it can merely ask "Is this a proof that the program is safe?" instead.

This is also approximately true of Idris. The thing that really helps Wuffs is that it's a pretty simple language without a lot of language features (e.g., no memory allocation and only very limited pointers) that complicate proofs. Also, nobody is particularly tempted to use it and then finds it unexpectedly forbidding, because most programmers don't ever have to write high-performance codecs; Wuffs's audience is people who are already experts.

Re: Use Your Type System

#230

I've used the approach described for uuids on a project and I liked it. We were using typescript so we went further using template literal types [1] type UserId = `user:${uuid}`; type OrgId = `org:${uuid}`; This had the benefit that we could add validation (basic begins with kind of logic) and it was obvious upon visual inspection (e.g. in logs/debugging). 1. https://www.typescriptlang.org/docs/handbook/2/template-li…

Typescript's template literal types are awesome when creating values that have to match a specific format.

But depending on the format it can sometimes be tricky to narrow a string back down to that format.

We have type guards to do that narrowing. (see: https://www.typescriptlang.org/docs/handbook/2/narrowing.htm..., but their older example is a little easier to read: https://www.typescriptlang.org/docs/handbook/advanced-types....)

If writing the check is too tricky, sometimes it can just be easier to track the type of a value with the value (if you can be told the type externally) with tagged unions (AKA: Discriminated unions). See: https://www.typescriptlang.org/docs/handbook/typescript-in-5...

And if the formats themselves are generated at runtime and you can use the "unique" keyword to make sure different kinds of data are treated as separate (see: https://www.typescriptlang.org/docs/handbook/symbols.html#un...).

You can combine `unique symbol` with tagged unions and type predicates to make it easier to tell them apart.

Post reply on HN