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…
Make the Type System Do the Work
71–80 of 141 posts
Re: Make the Type System Do the Work
#72I 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.
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
#73Did 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.
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
#74For 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
#759 / 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…
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
#77Earlier 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…
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
#78Earlier 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…
Re: Make the Type System Do the Work
#79This 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) // ...
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
#80This 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.
{-# 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.