Live data from Hacker News

Make the Type System Do the Work

nathan.ca

21–30 of 141 posts

Re: Make the Type System Do the Work

#21
I did just this very thing in C# a few months ago[1]. The problem I had was that it gets out of hand very quickly. I wanted to be able to divide distance by time and get speed out of it, but it is extremely difficult to satisfy all of the M-to-N relationships between types and still end up being usable.

I ended up abandoning it for the project I'm building, as I wasn't too clear on what the type of a secant of an angle in degrees should be and nobody on the interwebs seemed to care enough to take notice or answer my questions.

[1] https://github.com/capnmidnight/UnitsOfMeasure

Re: Make the Type System Do the Work

#22
I prefer to have a single class which stores an SI unit, as in

  final class Temperature {
    private final double kelvin;

    public static Temperature fromKelvin() { ... }
    public static Temperature fromCelcius() { ... }
    public static Temperature fromFahrenheit() { ... }

    public double getKelvin() { ... }
    public double getCelcius() { ... }
    public double getFahrenheit() { ... }
  }
This avoids the quadratic explosion of conversion methods.

A more complete example - https://github.com/dlubarov/daniel/blob/master/data/src/dani...

Re: Make the Type System Do the Work

#23
This article uses inheritance in C++ to ensure the types line-up, but I have really taken a shine to algebraic data types for my firm's data feed handlers.

Here is an example in C++11 that implements a market-data spec line-for-line.

  #pragma pack(push,1)
  
  struct Quote {
    char     symbol[16];
    uint16_t bid;
    uint32_t bidsize;
    uint16_t ask
    uint32_t asksize;
  };

  static_assert(sizeof(Quote) == 28, "Quote size wrong");
  
  struct Trade {
    char     symbol[16];
    uint16_t price;
    uint32_t size;
  };

  static_assert(sizeof(Trade) == 22, "Trade size wrong");
  
  struct Data {
    enum class DataType : char {
      kTrade = 't',
      kQuote = 'q'
    };

    uint64_t sequence_number;
    DataType data_type;
    union {
      Quote quote;
      Trade trade;
    };
  };
  
  #pragma pack(pop)
  
Now because I've disabled padding in the structs, the types will map directly to the proper byte locations as specified by the data vendor. There is no need for me to manipulate bytes!

And I can get away with a single cast:

  Data* data = reinterpret_cast(raw_buffer);
  switch (data->data_type) {
    case Data::DataType::kTrade: {
      Trade& trade = data->trade;
      std::cout quote;
      std::cout (data->datatype) 
So now the code resembles a match statement in OCaml, but will actually compile directly onto the underlying bytes. No data marshaling or anything like that.

Re: Make the Type System Do the Work

#24
The big problem (other than verbose code) with rolling up stuff into classes in C++ is that performance suffers. Many platforms will not pass small structs efficiently.

Inspired by Haskell's newtype, I drafted a proposal for C++14 which would have introduced native newtype to C++, but it got rolled into an omnibus paper (n3635) and appears to have been forgotten.

The proposal is modelled on strong enums, but generalizes them to all types. They are essentially strong typedefs with none of the weaknesses of wrapper types, restrict pointers, typedefs, and pragma disjoint.

    newtype NotInt : int; 
    NotInt k(5); // call ‘default’
    NotInt bar(NotInt);
    bar(k); // passes exactly as an ABI int would
    int baz(int&);
    baz(k); // Error! no conversion
	
While drafting this proposal, I realized it was a much more C++-like way of bifurcating aliases than 'restrict' keyword:

    newtype D1 : double; 
    newtype D2 : double; 
	
    void biz(D1 *x, D2 *y) {
        // Being separate, D1/D2 can be
        // assumed not to alias by optimizers
	...
    }
	
While playing with it, we came up with this proposed syntax (although I don't think this would get through the committee):

    [template ]
    newtype [NotT] : [(public|private)] T [= (default|explicit|deleted)] [{
      // only non-virtual member functions, no data or reference members (something like POD)
      // this->value has all of the operations of T, but only aliases with the new type, and implicitly converts to/from T prvalues.
    }] [optional_variable_name];

Re: Make the Type System Do the Work

#26
Did anyone else recoil in horror at how a simple function is turned into a type hierarchy? This is everything that's wrong with Java to me. Classes are all fine and well, but don't take it to extremes. Maybe the examples were just too trivial, but it really is a far greater evil than just using well named variables and a simple conversion function.

Re: Make the Type System Do the Work

#27

This article uses inheritance in C++ to ensure the types line-up, but I have really taken a shine to algebraic data types for my firm's data feed handlers. Here is an example in C++11 that implements a market-data spec line-for-line. #pragma pack(push,1) struct Quote { char symbol[16]; uint16_t bid; uint32_t bidsize; uint16_t ask uint32_t asksize; }; static_assert(sizeof(Quote) == 28, "Quote size wrong"); struct Trad…

This is incredibly common in embedded programming. Lots of times you'll have some binary packet come over the wire, the first field is a packet type specifier, you can then just cast the rest of your byte buffer to the proper type.

It is very unfortunate that C (and until recently C++) did not have a way to specify the size of enums. Basically this has resulted in crappy #defines being used for far too many years.

I always called what you did above "tagged types". A number of type systems actually work like that under the covers, objects get a type ID, and then a lookup function figures out what that type ID maps to.

Re: Make the Type System Do the Work

#28
post #24

The big problem (other than verbose code) with rolling up stuff into classes in C++ is that performance suffers. Many platforms will not pass small structs efficiently. Inspired by Haskell's newtype, I drafted a proposal for C++14 which would have introduced native newtype to C++, but it got rolled into an omnibus paper (n3635) and appears to have been forgotten. The proposal is modelled on strong enums, but generali…

I don't think there's going to be any performance difference at all compared to the basic case, which actually is another reason why this is so good. Ultimately, all these struct just hold a double, which means that in memory they're exactly a double and nothing more, there's no type tag or anything like that. And all the code is static so there is no vtable, just the double value. The compiler does the rest, not the runtime.

(Still, I'd love to see newtypes in C++)

Re: Make the Type System Do the Work

#29
post #26

Did anyone else recoil in horror at how a simple function is turned into a type hierarchy? This is everything that's wrong with Java to me. Classes are all fine and well, but don't take it to extremes. Maybe the examples were just too trivial, but it really is a far greater evil than just using well named variables and a simple conversion function.

I think the examples are just too trivial (or in this case, aren't extended to a greater library). Where they really come in handy is if you're using the types all over the place. If I were to create a Degrees class (with Celsius and Fahrenheit) just for representing it in a UI, I'd call it crazy. But if I'm doing conversions all over the place or creating a library for others to use, I would think the plumbing is worth it. Of course, that's true for most "plumbing" in code.

Re: Make the Type System Do the Work

#30

I prefer to have a single class which stores an SI unit, as in final class Temperature { private final double kelvin; public static Temperature fromKelvin() { ... } public static Temperature fromCelcius() { ... } public static Temperature fromFahrenheit() { ... } public double getKelvin() { ... } public double getCelcius() { ... } public double getFahrenheit() { ... } } This avoids the quadratic explosion of conversi…

One way to do this is to have a super class for each measurement type with a canonical measurement. So Temperature,Mass,Time,Distance etc it's slower but this way you can always get any measurement to come out without doing kg>lb, grams>lb, ounces>LB because it ends up as kg>kg>lb, grams>kg>lb etc.
Post reply on HN