10 ms·
Let the Type System do the Work
- sgt101 12y agoYus. Dynamically typed languages are expressive, quick and fun. But strict static typing is like a seat belt, it's annoying, but it just might save your life. Also ceremony code is there for a reason; that reason is not to annoy you, it is to make sure that those who come after you know what it is that you have done and why. Also: comments. Also: documentation. Also: UML.
- masklinn 12y ago> But strict static typing is like a seat belt, it's annoying, but it just might save your life. The problem with saying "static typing" without further precision is on one axis it ranges from C where the steatbelt is made of paper to ATS or Idris where the "steabelt" is a zero-zero ejection seat, and on an other axis it ranges from Java where you have to braid the steatbelt from raw fibers any time you sit down to MLs or Haskell where the seatbelt magically appears around you.
- seanmcdirmid 12y ago> MLs or Haskell where the seatbelt magically appears around you. Well, only if you don't need nominal subtypes.
- masklinn 12y agoYeah, conditions may apply, same for the other categories. Then again, we're (literally) in car-analogy land here so meh.
- DanWaterworth 12y agoHonest question; Could you give an example where you need nominal subtypes?
- seanmcdirmid 12y agoIf you like doing real object-oriented programming, then nominal subtypes are mighty useful.
- spacemanaki 12y agoWell here's another honest question: what's real object-oriented programming and where can I learn more about it?
- seanmcdirmid 12y agoThere is an entire lineage of real object-oriented languages with nominal types starting from the dynamically typed smalltalk (research on which where we learned H&M was horribly ill suited to OOP in the first place). Or if you prefer, just look at how you use your natural language to name things and reason about the world.
- acjohnson55 12y agoOn the other hand, so many people have abandoned the most common forms of OOP. See for example the story from yesterday [1]. Many writers have written about how brittle object hierarchies are in anything nontrivial, [2] for example. I will say though that newer models of OOP, focusing on interfaces and composability of behavior, seem to have quite a bit of steam left in them. [1] https://news.ycombinator.com/item?id=7618933 https://news.ycombinator.com/item?id=7618933 [2] http://raganwald.com/2014/03/31/class-hierarchies-dont-do-that.html http://raganwald.com/2014/03/31/class-hierarchies-dont-do-th...
- seanmcdirmid 12y agoDon't count OOP as being dead yet, the FP stuff is useful (I use it myself), but we have our own better abstractions coming out, like having more fun with mixin-style inheritance. The FP + OOP crowd is pretty vocal, I'll give you that.
- altcognito 12y agoThe problem with magic seatbelts is that all the tooling surrounding the magic has to know how the magic works as well.
- NAFV_P 12y ago> The problem with saying "static typing" without further precision is on one axis it ranges from C where the steatbelt is made of paper to ATS or Idris where the "steabelt" is a zero-zero ejection seat, and on an other axis it ranges from Java where you have to braid the steatbelt from raw fibers any time you sit down to MLs or Haskell where the seatbelt magically appears around you. Regarding C, it is weakly typed so it doesn't care whether you feed it apples or habanero chillis, the static type system isn't for safety, it's for improving compilation speed (which was often a big headache in the 1970's). It sticks with the methodology of C: if you make a mistake you pay for it dearly.
- camus2 12y agoDynamic typing is fine,what is not is type coercion and weak typing.But Dynamic vs Static is not the most important debate.
- deleted 12y ago[deleted]
- dllthomas 12y agoYou lost me at UML.
- benaiah 12y agoThe following is based on conjecture, as I'm not old or well-read enough to be sure of this, but it seems to me that the original purpose of type systems got muddled up by the tremendous popularity, mostly in Windows systems, of "hungarian" variable naming style. When your "type system" consists of adding three letters to the beginning of a variable name, you don't have a way to make descriptive types. Java, in most of the ways I've seen it used, is essentially hungarian notation enforced as a language feature. I realize that variable naming and type systems are two different things, but it seems many programmers never realized the point of type systems (expressing the type for the programmers sake) because they only ever saw it used to distinguish abstract primitives. For a long time, I had trouble understanding that hungarian notation wasn't a type system, because they seemed to do precisely the same thing - that's how limited my understanding of types was. The kind of style seen in this article was alien to me for a long time, but it was enlightening to realize that the type system is there to help me out, not just make me type a bunch of unnecessary crap. tl;dr: I really need to learn Haskell.
- jarrett 12y agoI think what you're getting at is the distinction between this: float x; and this: kilogram x; The former, as you say, only tells you about the underlying representation. It says "this is a float, so the computer should store it in such and such a way." That's fine, but it doesn't tell us enough. The latter example is far more useful. In my imaginary language, the declaration implicitly tells the computer to store the value as a float, because the kilogram type has been defined as such elsewhere. But that's not all it does! It tells us and the compiler that this float represents a real-world quantity measured in kilograms. It prevents us from mistakenly passing kilograms where a pounds were expected, or seconds where kilograms were expected. On the Haskell front, you might be interested in the Dimensional library, which does just that. It also works elegantly with multiplying and dividing units. E.g. if you have a miles value and an hours value, you can divide to get a miles per hour value.
- benaiah 12y agoYeah - this is exactly the kind of distinction I was trying to make. Reminds me of Abelson's famous quote, "Programs must be written for people to read, and only incidentally for machines to execute." That's an idea that's largely forgotten when it comes to type systems. Thanks for the pointer - I'll be sure to check out the library as I dive into Haskell over the next few months.
- th3iedkid 12y agosome of the best pieces in type systems (and model theory too!) were from benjamin pierce and his book on type-systems.
- rossjudson 12y agoThe type system is what the compiler (or interpreter) knows about your program. Sophisticated type systems know a lot, and can tell you a lot. Sophisticated does not mean typing a lot. When building code for the long term, you really want to focus on techniques that make it impossible to misuse an API, whenever it's possible to do that. This article nicely calls out one such technique. Generic typing can be used to (try to) force the consumer of an API into correct usage. Judicious use of final and abstract in the land of Java can be used force/guide eventual overriders of an abstract class down the right path -- they'll get compilation errors if they don't at least implement the right methods. Whenever you find yourself writing an assert method, take a beat and figure out if the type system could have turned that into a compilation error instead.
- dllthomas 12y ago"The type system is what the compiler (or interpreter) knows about your program." Compilers know a lot more than just what is represented in "the type system" (as it's typically thought of, anyway). Control flow (basic block analysis, &c) for instance. Which isn't really taking away from your larger point - certainly, things represented in the type system are things the compiler will know about. "Whenever you find yourself writing an assert method, take a beat and figure out if the type system could have turned that into a compilation error instead." Possibly with a _Static_assert (or static_assert) as if C11 (/C++11)! Obviously there are substantial limitations still, but it's great to be able to pull more to compile time cleanly.
- be5invis 12y agoAccording to Curry-Howard correspondence, types are conclusions and programs are proofs of the conclusion. Any program is corresponded to a deduction procedure, such as a Hilbert-style proof or natural deduction proof.
- sgarlatm 12y agoThis discussion reminds me of a 2005 article from Joel Spolsky called "Making Wrong Code Look Wrong". It has a similar point to this article: there are things we can do as programmers to make our code less error prone. It's definitely worth a read if you're interested in this topic and haven't read Joel's article before. http://www.joelonsoftware.com/articles/Wrong.html http://www.joelonsoftware.com/articles/Wrong.html
- jackcarter 12y agoI've seen this paradigm called "Tiny Types" before: http://darrenhobbs.com/2007/04/11/tiny-types/ http://darrenhobbs.com/2007/04/11/tiny-types/
- darkestkhan 12y agoI'm actually surprised that people learn about this only now... It is quite standard (and very common) thing to do in Ada. But then we can "subtype Strength is Natural range Natural'First .. 32;" since 80s (numeric type with all operators defined with permitted range of values from 0 to 32; range checks are performed at runtime by compiler - but most of them are optimized out anyway so penalty for them is in single digit percents of performance). Or if you prefer example from that site: "type First_Name is new String;"
- iaskwhy 12y agoWhile I agree with the idea of the article, what's the logic for not using "small types" for all parameters? Or, to put it another way, how do you decide a given method should have "small types" used for parameters? Also, although it makes the code much more verbose, named parameters (as used in, for example, Objective-C) fix any eventual bug with refactoring methods' signatures, right?
- benaiah 12y agoThe example given in this post doesn't show this, but what using types allows us to do is understand what a specific value is throughout its whole lifetime through all sorts of functions. We know that this vector is a velocity, and our compiler knows that too, so we can't unwittingly treat it as an acceleration vector further on down the line, whether that's done when passing it to another function, declaring variables, adding things together, or what-have-you. You are right that the specific example shown in the article can be achieved with simple named parameters, but that's a very small and specific application of the very broad protection that proper type use gives you.
- TheLoneWolfling 12y agoNamed parameters don't help in the case where you have (for example) one function returning (pos, size) and another function expecting (size, pos).
- Terr_ 12y ago> Or, to put it another way, how do you decide a given method should have "small types" used for parameters? I think a good rule of thumb is to ask yourself what the "dimension" or "unit" the parameter has. (See also: Dimensional Analysis.) You never want to pass 4.33f radians into a function expecting 4.33f newtons! A few more examples: Distance in meters, Distance in feet, Speed in m/s, speed in yards/minute, Acceleration in m/s^2, Acceleration in cm/s^2, Mass in grams, Weight in pounds, Force in newtons, Force in dynes, Temperature in Celsius, Temperature in Fahrenheit, Angles in radians, Angles in degrees.... "But I don't do physics programming!", you say? Well, there's plenty more: Time in seconds, Time in milliseconds, Distance in pixels, Distance in inches, Numeric ID of a Foo, Numeric ID of a Bar, Bytes in UTF8, Bytes in ASCII, US currency in dollars, US currency in cents, Euros, interest rate (yearly), interest rate (monthly)... And that's not even getting into industry-specific dimensions that might exist.
- Serow225 12y agoSee also this blog post about using using single-member structs in C to achieve similar goals: http://spin.atomicobject.com/2014/03/25/c-single-member-structs/ http://spin.atomicobject.com/2014/03/25/c-single-member-stru...
- NAFV_P 12y agoI came across this a few weeks ago, I think the first example is to do with arrays being second class. On the other hand, if you give data to a C-programmer, they can locate its position in RAM. Give an assembly-programmer a simple data-type, they can point to a specific register(s) in the processor.
- bglazer 12y agoFrom a performance point of view, is there a significant penalty associated with these "small types"? For example, kilogram as wrapper around float?
- radicality 12y agoIt depends on the implementation. I'm not familiar with Scala, but in Haskell, using `newtype`s to achieve this creates absolutely no extra runtime overhead, only compile-time overhead when the types are checked.
- axman6 12y agoThis isn't completely true, there are issues which come up with code like newtype Foo = Foo Int map (\(Foo x) -> x) listOfFoos which should be a no-op at runtime because the representation of Foo x and x are the same, but due to the newtype, the traversal does need to occur. [1] discusses a way to avoid these problems. Basically these newtype should only ever be needed during type checking, and should really be erased after that; once you've type checked everything, then you know your program won't ever treat something which is a non Foo Int as an Int, so you can then get rid of the compile time tag and gain better optimisations. [1] http://research.microsoft.com/en-us/um/people/simonpj/papers/ext-f/coercible.pdf http://research.microsoft.com/en-us/um/people/simonpj/papers...
- kasey_junk 12y agoThe way these are implemented, the short answer is yes. There will be more memory pressure on the garbage collector in Scala. The long answer is, "mmmm maybe, you should test it" because due to escape analysis, eden generation collecting speed, JIT, etc. It can get very complicated very fast. It can also be very dependent on what you mean by "significant penalty" and "performance". As an aside, Scala does allow for compile time only small types in the form of AnyVal's. They won't help in the examples in the article, but in the simpler kilogram wrapper around float it will and there will be no performance penalty as it will be elided by the compiler.
- 12y ago
- hcarvalhoalves 12y ago> val player = new Player(new Vector2(100, 100), new Vector2(50, 50)) > No problems here. The first noticible crack in the system comes after a few weeks vacation away from this code. You come back, and you want to change the starting point of the player. Will you remember which Vector2 to change? player = Player(position=Vector2(100, 100), size=Vector2(50, 50)) The problem is not typing here, it's lack of named arguments.
- TheLoneWolfling 12y agoI'd disagree with you: named arguments are good, but they don't save you in a lot of cases. For example when you have one function returning (pos, size) and another function expecting (size, pos).
- hcarvalhoalves 12y agoNamed arguments is syntatic sugar for packing/unpacking data structures in some languages, so you can leverage that. In Python: >>> from collections import namedtuple >>> Vector2 = namedtuple('Vector2', ('x', 'y')) >>> PlayerOptions = namedtuple('PlayerOptions', ('position', 'size')) >>> opt = PlayerOptions(position=Vector2(100, 100), size=Vector2(50, 50)) >>> Player(**opt._asdict()) The order arguments are passed doesn't matter anymore. You can also enforce this calling convention with a constructor signature like this (in 3): >>> class Player(object): >>> def __init__(self, *, size, position):
- q845712 12y agoi agree that in languages without compiler defined types, but with other tools such as named arguments, the class of errors described here can be to some extent avoided by making good use of named arguments. I'm gonna do this more often in my python code!
- jasode 12y agoNamed arguments by themselves do not fundamentally prevent data type errors when the underlying types are identical. Short variable names (v1, v2) or some other cosmetic ambiguity will deceptively compile with named arguments as the only defense. Both of the following would compile successfully using named arguments but one of them is semantically wrong: Player(pos = v1, size = v2); Player(pos = v2, size = v1); Using a compiler's static type checking enforces correct semantics more than named arguments. Of course, this only works if the programmer creates new differentiated types so that they are no longer identical (which was the crux of the article.)
- ZenoArrow 12y agoF# allows you to do these 'small types' in a few different ways, including unit annotations... http://fsharpforfunandprofit.com/posts/units-of-measure/ http://fsharpforfunandprofit.com/posts/units-of-measure/
- charlieflowers 12y agoThe general principal makes a ton of sense: let each line of your code express as much of your intent as possible. That way, when you have a huge codebase, there are all these useful little "hooks" throughout it that will be useful when you need to refactor. Your codebase will "say" more, and therefore your tools will "know" more, and therefore your tools can do more for you.
- charlieflowers 12y agoOh, but one more thing. It's very nice if the abstractions you use to do this are resolved down to nothing at runtime. So there's no penalty at runtime for the precision you added to the code. Let the compiler do the hard work so that you can say "Position", but at runtime, the code is as if you had written "Float".
- 205guy 12y agoOne language that enforces strong typing is Ada. It has compiler-time and run-time type checking. It also had many other features to make the code less error-prone and maintainable. It was used by the DoD and also used in critical applications, such as nuclear power plants. The early versions of Java reminded me very much of Ada.
- kjs3 12y agoI'm hoping programming grows up enough to remember languages like Ada. Dynamic languages are useful for "get something running quick, then work on getting it right". Ada makes you get it right up front, or it doesn't even compile. The former is good if you have to bang out a script this afternoon; the later is good if you have to maintain a code base for 10+ years, with multiple programmers, and some assurances about reliability.
- axman6 12y agoAs far as I know, there's no runtime type checking of Ada, but there is runtime value checking to ensure values are within the specified ranges/adhere to the specified predicates (a very cool feature; you can specify that a type is always even, and encountering an odd value will cause an exception).
- darkestkhan 12y agoOh, there is runtime type checking, though usually it is optimized out at compile time: it is for subtypes (which is mostly range checks) and checks of tags for tagged types (usually in class wide subprograms). Though both of them are often optimized out.
- exabrial 12y agoType systems in languages make me less productive. Unless I have to provide support, add features, scale my application, write bug free code, hire additional developers, or explain my thought process to anyone else.
- axman6 12y agoRight, it's well known that type systems are essentially useless, except when you need to write high quality code, and do all those things you mentioned. Basically they're unnecessary (but only if you use the term unnecessary very literally).