Live data from Hacker News

Make the Type System Do the Work

nathan.ca

71–80 of 141 posts

Re: Make the Type System Do the Work

#71
post #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…

The more common term is "tagged union". http://en.wikipedia.org/wiki/Tagged_union

Re: Make the Type System Do the Work

#72
post #62

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…

But this solution doesn't solve the original problem described in the article. With your API, it is easy for a programmer to use a temperature in Celcius as a temperature in Kelvins and the type system can't catch it.

Well, most code can just pass around Temperature objects without dealing with any particular units. There's no opportunity to mix up units there.

When some code needs to actually get a number out, say for the purpose of logging, it's possible to screw up with either approach.

With the author's classes, you could do

  void printTemperature(DegCelsius temperature) {
    log("Temperature in Kelvin: %f", temperature.getDegrees());
  }
With this Temperate class, you could do

  void printTemperature(Temperature temperature) {
    log("Temperature in Kelvin: %f", temperature.getCelcius());
  }
As the author says, "wrong code should look wrong." I think both of these look pretty wrong.

Re: Make the Type System Do the Work

#73
post #49
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.

It's nice how in Go you can alias a type to int, yet it is its own distinct type with compile time checks and explicit conversions. Definitely takes away all the boilerplate.

> Definitely takes away all the boilerplate.

Until you need to write generic code. Then it is casts everywhere and boilerplate to satisfy interfaces.

Re: Make the Type System Do the Work

#74
Does static typing have inherent costs?

For instance, there are some desirable properties often present in dynamic languages like hot code loading, or the ability to make code updates independently. Not many statically-typed systems are good at these things, or at least the static typing doesn't work well across boundaries (like a network, etc.).

Can these be reconciled? Can something have all of the benefits of, say, haskell and erlang at once?

Re: Make the Type System Do the Work

#75
It might be because I only use C family languages for very low level highly optimized stuff, but I found it very weird that a blog post about types would force all those useless casts from int to double.

9 / 5 in lieu of 9. / 5. or 1.8 will come back and bite you!

Re: Make the Type System Do the Work

#76

"'Dog is-a Mammal' ... actually fairly sound" I disagree in the context of OOP inheritance. (This example uses shapes, because the idea of mutating mammals gets a little strange.) Let's say you have a Circle class and an Ellipse class. A Circle inherits from an Ellipse, of course, because a Circle is-a Ellipse. By specializing as a Circle, we get extra reader methods such as getRadius(). Great. But what about mutator…

The circle vs ellipse problem is rather artificial and, if anything, it shows that modeling with types and everyday intuition are two different things.

If the rest of your program can handle general ellipses, it should also be able to handle ellipses having the same minor and major radius (i.e., circles). The obvious solution is to not have the Circle class and _maybe_ equip Ellipse with IsCircle() method. (Though, why would you care?!)

See the C++ faq lite items 21.6 -- 21.8. (A quote from 21.8: "Here's how to make good inheritance decisions in OO design/programming: recognize that the derived class objects must be substitutable for the base class objects. ")

Re: Make the Type System Do the Work

#77

Earlier quoted context omitted.

"Dog is-a Mammal" (or "Circle is-a Ellipse") is sound, what is unsound is supporting mutations that alter identity. If you change the minor axis of an ellipse, it isn't the same ellipse . The idea of a mutating squash method of the type derived is therefore unsound in the context of the domain being modeled, as it would alter identity. > (This example uses shapes, because the idea of mutating mammals gets a little st…

"what is unsound is supporting mutations that alter identity" EDIT: included code for clarity I can make essentially the same example with concrete entities that exist in time and space, as well: void f1() { GasolineVehicle vehicle(myEngine); f2(vehicle); cout After replacing the engine with a motor, MPG no longer makes sense. At the time you're writing the Vehicle class, it seems perfectly reasonable to define a set…

> At the time you're writing the Vehicle class, it seems perfectly reasonable to define a setPropulsionDevice() method

Are you claiming that it seems reasonable to be able to replace a car engine with a jet engine without changing anything else? (The car's hull, construction, etc., and after all these modifications it's not the "same" car anymore.)

Re: Make the Type System Do the Work

#78
post #45

Earlier quoted context omitted.

This method also create a lot of object churn. For reasonably small systems that can be okay, but you have to be mindful of it.

Would it be possible to do this as a type-alias type thing that disapears at compile time? As far as I am aware, Java has no support for such type aliasing, but it should be possible to add a pre-compiler phase to your build process that replaces these types with what they are aliases of. The only part of this that seems complicated is the type checker, which would need to be aware of the difference between AccountId…

I could see it being done with annotations + processing them perhaps, as some frameworks do for database properties such as index/uniqueness. An object for every scalar datatype is just overkill, even if it solves a legitimate problem.

Re: Make the Type System Do the Work

#79
post #52

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 a neat trick, but it will segfault in architectures where alignment is enforced (like MIPS, ARM, etc) if the input buffer is not suitably aligned. One relatively cheap way to sidestep this is to memcpy into a stack-allocated struct: Data data; memcpy(&data, raw_buffer, sizeof data); switch(data.data_type) // ...

well, there are 2 other options available:

one is to use "--pointer_alignment=1" so that all accesses via the pointer are treated as unaligned OR

use "--no_unaligned_access" flag to tell the compiler to not knowingly generate unaligned access

Re: Make the Type System Do the Work

#80

This is a good trick but it needs to be used judiciously or you'll end up with lots of boilerplate. I particularly like Go's type system, though, because it makes it very simple: type celsius float64 There's no overhead and it doesn't attempt to prevent you from doing any calculations as a float64; the type checking just prevents direct assignments from a celsius type to another type.

On the other hand, Go doesn't have a way to write code that abstracts over types, and my (albeit limited) experience with Go is that most of the boilerplate is due to the language's poor type system. If you use a language with a better type system (Haskell is the first that comes to mind, though there are others), you can actually do this sort of thing without the boilerplate code.

Haskell still has some boilerplate - actually quite a lot if you don't use -XGeneralizedNewtypeDeriving (which has known problems when used with other extensions). With newtype deriving, this is about as simple as it gets:

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}

    class Degrees deg where
      toK :: deg -> K
      fromK :: K -> deg

    newtype K = K { unK :: Double } deriving (Show)
    instance Degrees K where
      toK = id
      fromK = id

    newtype C = C { unC :: Double } deriving (Show)
    instance Degrees C where
      toK = K . (+ 273.16) . unC
      fromK = C . (flip (-) 273.16) . unK

    newtype F = F { unF :: Double } deriving (Show)
    instance Degrees F where
      toK = K . (+ 273.16) . (* (5/9)) . (flip (-) 32) . unF
      fromK = F . (+ 32) . (* (9/5)) . (flip (-) 273.16) . unK
Without newtype deriving (or other generic deriving extensions), one would need to implement instances of {Num, Real, Fractional, RealFrac, Floating, RealFloat} for all three types, which do nothing but map to the Double equivalents.
Post reply on HN