openlang

Units of measure as cheap ids

For integer ids I've been trying units of measure instead of single-case unions, and I'd like a second opinion before it spreads. CustomerId of int allocates unless it's marked [<Struct>], and every use goes through a constructor or a match. With [<Measure>] type customerId the id is int<customerId>, the unit is erased at run time, and passing an int<orderId> where a customer id is expected fails to compile.

The costs I've found so far are small but real. Only numeric types take a unit, so string ids still need a union. Getting a plain number back means int id, which reads fine. And a reflection-based JSON serializer sees a plain int, so the unit is reapplied by hand on the way in, which is exactly the spot where a mixup could slip through.

Maybe units for internal ids and a union at the edges where values come from outside? I'm not sure the split is worth explaining to the next reader.

The conversation continues 1

Follow the reasoning
  1. Permalink

    One thing the unit lets through that the union doesn't. Arithmetic.

    [<Measure>] type customerId
    
    let a = 41<customerId>
    let b = 1<customerId>
    let c = a + b   // int<customerId>, compiles

    Adding two customer ids type-checks and produces a third customer id, and a * b gives int<customerId ^ 2>. Units were made for quantities, so all the usual arithmetic stays available. CustomerId of int has no + unless someone writes one, and a pattern like let (CustomerId n) = id makes the place where the number comes out visible. For ids that only get compared and passed around, I'd lean toward the union, with [<Struct>] for the allocation worry.

Other sides of the same question

  1. A helper ported from Java picks a different overload in Groovy

    Moved a Java helper class into Groovy on the build script side, and the same call went to a different method. Laid out in ord…

    Replies 3groovyjava
  2. An empty cell equals zero and the empty string

    Inherited a macro that flags order rows with a zero quantity, and it flagged every blank row too. The check was If ws.Cells(r…

    Replies 7vba
  3. Validating JSON before the discriminated union

    A type assertion only tells the compiler what to believe. An external response starts as bytes and says nothing about its var…