Live data from Hacker News

Make the Type System Do the Work

nathan.ca

61–70 of 141 posts

Re: Make the Type System Do the Work

#61
Some talk on the racket mailing list was had for "numbers with dimensions[1]" in which contracts were being bent to do calculations which respected units [2]. The Frink language was also mentioned [3].

[1] https://groups.google.com/forum/#!topic/racket-users/mnId0ux...

[2] https://github.com/Metaxal/measures#4-dimensions-and-contrac...

[3] http://futureboy.us/frinkdocs/#SampleCalculations

Re: Make the Type System Do the Work

#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.

Re: Make the Type System Do the Work

#63

"'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…

This is elegantly solved with immutable data types. The squash method of an Ellipse can be defined to return an Ellipse (a new instance, the original is never modified in place). This will work fine even when Circle inherits from Ellipse.

Re: Make the Type System Do the Work

#64
post #50

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…

> static_assert(sizeof(Quote) == 28, "Quote size wrong"); A good reminder that trying to "let the types do the work" in C++ is an oxymoron. If you're so keen on algebraic data types, there are languages much better suited to that than C++.

> If you're so keen on algebraic data types, there are languages much better suited to that than C++.

Yes, you are quite right.

However it only works when one works alone.

Usually the choice of a language is driven by what the customer requires as technology, what the team members are capable of using or willing to learn, finally what the boss allows.

Re: Make the Type System Do the Work

#65
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…

> It is very unfortunate that C (and until recently C++) did not have a way to specify the size of enums.

It had compiler specific switches to set the size of all enums globally, which helped exactly nobody.

Re: Make the Type System Do the Work

#66
post #40

Earlier quoted context omitted.

F# has this, I wonder how they did it?

They couldn't do it. The compiler does static verifications that, say, a value of type Meter when divided by a value of type second results in a value of type Meter/Second, where '/' is a custom concept built into the F# compiler. The .NET runtime knows nothing about these custom types.

Yeah, that's pretty much exactly the issue I ran into. The problem is that the standard .NET type semantics can only infer "up" the inheritance hierarchy.

I had gotten really close. I had types like

    Base
    Length: Base
    Meters: Length
    Feet: Length
    
    Compound
    Area: Compound where T: Length
    SquareMeters: Area
    SquareFeet: Area
and had defined all of my math as generic extension methods of the Base class so that the types would compose:

    Compound Multiply(T a, U b) where T : Base where U : Base
    T Divide(Compound, U b) where T : Base where U : Base
    
That went a long way towards getting fairly concrete types out of only a few lines of code. But I couldn't get it the rest of the way. C# explicitly forbids user-defined typecasts between types in an inheritance chain with each other, so while multiplying two Meters got me a Compound, it was not then possible to take a Compound and convert it to anything like Area or SquareMeters automatically.

Re: Make the Type System Do the Work

#67

"'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…

"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 strange.)

Mutating shapes is strange, because geometric shapes don't tend to have attributes that can be mutated without effect on identity (if you change their features, they aren't the same shape.)

Mutating animals is more normal; because a dog is a concrete thing that exists in time and space and not an abstract ideal the way a shape is, there are attributes of a dog that can change without its identity changing, so mutating methods on a Dog can make sense.

Re: Make the Type System Do the Work

#68
post #53

Earlier quoted context omitted.

They couldn't do it. The compiler does static verifications that, say, a value of type Meter when divided by a value of type second results in a value of type Meter/Second, where '/' is a custom concept built into the F# compiler. The .NET runtime knows nothing about these custom types.

Ah, I see. Looking at how something similar was implemented in Haskell, they used phantom types and functional dependencies (in the type system) to achieve this - I'm not sure F# has those capabilities.

F# has that capability, but .NET does not, so once the F# compiler has done its checking, it drops the info on the floor and you have nothing at runtime.

Now, this might not necessarily be a bad thing for F# (I don't know, I'm not an F# user). The runtime type information in .NET is great, but it's all for reflection to be able to build things that a stronger type system like F# has, or a macro system, would be able to handle.

Re: Make the Type System Do the Work

#69
post #41

I think the major problem this runs into is that it doesn't necessarily help with the actually hard parts of programming. That is, sure, mistakes have been made with units. More mistakes are made in other areas, though. Usually amusingly more basic areas. The question seems to be whether encoding at the type level will help with these areas. It seems the conflict is the idea that fully proving a solution is superior…

Type checking always happens at some point in every language, dynamic or static. At some point, a routine gets executed on some form of data, and the success of that routine is predicated on the data being of the right type.

In dynamic languages, you end up writing a whole raft of tests that are not much more than type assertions that are otherwise done for you by the compiler in a statically typed language.

For statically typed languages, the type check is a type of test that just so happens to be very succinct and easier to write than it is to skip. In dynamic languages, it's harder to write a test for a type assertion than it is to trust the duck is a duck, give it a punch, and hope it quacks.

It makes sense, especially for a reusable library, to try to offload as much to the static type checker as possible. It always runs and can't be skipped. As you say, "especially if you have the right tests," a static type checker forces you to have the "right tests" for at least a small part of your code, leaving you to write fewer tests for the rest of it.

Re: Make the Type System Do the Work

#70

"'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…

"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 setPropulsionDevice() method, and there's no hint that calling it would change the vehicle's identity. Only when you define the subclass does it turn into an identity problem, but then it's too late.

Also, if you think the Circle/Ellipse example is not compelling enough, how would you structure those two classes? Would one inherit from the other, and if so, in which direction?

Post reply on HN