10 ms·
Make the Type System Do the Work
- EdwardDiego 13y ago> The only problem is that you end up with a variable that fails to describe itself better than “I’m a number”. Our application servers need to know about updated entities, so we broadcast notifications when an entity has been updated - but we prefer to leave it up to the individual applications to retrieve the updated entity as they see fit. So we're often dealing with numerical ids of entities, and we've had a few bugs arise from code like so public void something(long accountId, long websiteId) When people have accidentally used the wrong long in the wrong place. So we've now moved to using simple wrapper types around aforementioned longs - public void something(AccountId accountId, WebsiteId websiteId) It does come at a little bit of cost as we're passing these things around a messaging system, so now we have to think about how they are serialized (Java serialization is problematic, and there are occasional religious wars about GPB vs. JSON), but it leverages the type system to make it every explicit what this number represents.
- eropple 13y agoThis method also create a lot of object churn. For reasonably small systems that can be okay, but you have to be mindful of it.
- gizmo686 13y agoWould 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 and long.
- yareally 13y agoI 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.
- eropple 13y agoYou could make it a pre-compile step in Maven, maybe. But it'd be gross.
- mapcar 13y agoSorry, what language are these example given in?
- deleted 13y ago[deleted]
- deleted 13y ago[deleted]
- MBlume 13y agoC++
- deleted 13y ago[deleted]
- rcfox 13y agoThe Boost.Units[1] library takes this even further, allowing you to do things like divide a length by a time quantity and get a velocity, again with compile-time type checking. [1]: http://www.boost.org/doc/libs/1_55_0/doc/html/boost_units.html http://www.boost.org/doc/libs/1_55_0/doc/html/boost_units.ht...
- EdwardDiego 13y agoF# has similar also: http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Me...
- imsofuture 13y agoTo take this even further, look at OCaml's strong, inferred typing.
- coolsunglasses 13y agoTo take this to the current state of the art, look at Haskell, Agda, and Idris. Latter two being dependently typed. Steps for learning Haskell: https://gist.github.com/bitemyapp/8739525 https://gist.github.com/bitemyapp/8739525
- dustingetz 13y agoyo switch the gist to markdown to get word wrap
- coolsunglasses 13y agoDone.
- Buttons840 13y agoWhat are your impressions of the three languages? Personally, I'm learning Haskell first, because Haskell is the most popular of the three and there is plenty of good learning material. As far as Adga and Idris go, I think Idris looks more appealing.
- tel 13y agoLearn Haskell first. When you've gotten your head at least partially around it then the latter two become much more approachable. Programming in a dependently typed language is not simple if you're not used to the algebraic methods they're based on. That said, Idris seems to be aiming to be much more "practical" than Agda. Also, all that said, once you start getting to the point where you're really getting comfortable with Haskell types you absolutely should learn Agda or Idris since they do advanced types far better than Haskell does. Many confusing Haskellisms become obvious in the light of Agda/Idris.
- mcguire 13y agoCare to make any comparison to ATS? I have been playing with it a bit lately and having a good time.
- wwwtyro 13y agoInteresting. What about more complex units, e.g. acceleration - m/s^2?
- saltylicorice 13y agoNo.
- deleted 13y ago[deleted]
- skybrian 13y agoThis 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.
- hdevalence 13y agoOn 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.
- sparkie 13y agoHaskell 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.
- tel 13y agoGeneralizedNewtypeDeriving's problems are fixed in the newest GHC which just had it's first RC a few days ago. It introduces a "roles" system which tracks which types are allowed to use GND without breaking abstraction boundaries. Roles are mostly invisible to end users as well.
- greatsuccess 13y agoThis is fairly the premise of OOP on display. And fairly basic at that. Im not sure whats to be learned here except that in the real world, your representations will always need to be extended and subclassing is usually a bad hammer. Interfaces are better but extend poorly through subclassing as well. Composition is infinite but difficult to define over time when things change (usually resorts to more subclassing or more injection strategies). I wish this article were valuable to solving the actual problem but its just a primer on OOP vs using primitives which is not particularly instructive or useful to solving real world problems.
- edderly 13y agoIn some environments beware of creating new types and objects: http://developer.android.com/training/articles/perf-tips.html#ObjectCreation http://developer.android.com/training/articles/perf-tips.htm...
- ygra 13y agoYes, I noticed that some time ago and ended up rewriting parts of the application (and also avoiding generics, instead opting for HPPC). What annoys me a bit is that Dalvik has dramatically different performance characteristics from normal Java which necessitates relearning all this stuff. Nice article though and I wish I had found it sooner.
- jimmaswell 13y agoThis seems to paint the JIT as something that hurts performance instead of helps it, is that true on Android?
- edderly 13y agoIt relates to the behavior of the garbage collection scheme used by dalvik which is a mark and sweep approach. When the GC runs, that’s all the VM is doing. It’s pointed out that a generational garbage collector would be more efficient allocating memory presumably there is a trade off in terms of the overall memory footprint.
- moron4hire 13y agoI 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 https://github.com/capnmidnight/UnitsOfMeasure
- jimmaswell 13y agoThe type of a trig function is just a scalar, no unit.
- platz 13y agoF# has this, I wonder how they did it?
- magic_haze 13y agoThey 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.
- platz 13y agoAh, 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.
- moron4hire 13y agoF# 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.
- dlubarov 13y agoI 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/daniel/data/unit/Duration.java https://github.com/dlubarov/daniel/blob/master/data/src/dani...
- Retric 13y agoOne 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.
- hrjet 13y agoBut 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.
- dlubarov 13y agoWell, 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.
- 13y ago
- chrisaycock 13y agoThis 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<Data*>(raw_buffer); switch (data->data_type) { case Data::DataType::kTrade: { Trade& trade = data->trade; std::cout << trade.symbol << ' ' << trade.price << ' ' << trade.size << std::endl; break; } case Data::DataType::kQuote: { Quote& quote = data->quote; std::cout << quote.symbol << ' ' << quote.bid << ' ' << quote.ask << std::endl; } default: std::cerr << "Unknown message type: " << static_cast<char>(data->datatype) << std::endl; } 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.
- lukego 13y agoThat's how I write LuaJIT code for networking and device drivers too. Very neat.
- mlvljr 13y agoWhat if your bytes are 16 bit on a given platform? ;)
- fmap 13y agoIf I remember correctly there was even a special clause in the C standard definition of union types which allows you to write: struct A { Data header; ... } struct B { Data header; ... } union AB { Data header; A a; B b } and then access the header field in AB. If this is syntactically the first field in each of the constituent records the result is well-defined. This is a great solution if you need to map to a specific wire format. If you do not have that constraint and have code which uses algebraic data types a lot you should probably switch to OCaml or Haskell. There are specific optimizations to work with complex matches and for representing algebraic types in a compact and efficient way. In C++ the compiler has to work with reinterpret_casts and cannot change the representation of your data types (much). Doing the optimizations by hand is possible, but chances are that a simple-minded OCaml prototype will already perform reasonably well.
- clord 13y agoThe 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 <typename T>] 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];
- gordaco 13y agoI 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++)
- comex 13y agoIt can make a difference due to weird ABIs. For example, in the ARM procedure call standard, 64-bit values can be returned in r0 and r1, but 64-bit structures can't (no matter whether they contain two 32-bit values or just one 64-bit value). However, I'd call that fairly niche - it's hard to imagine an application that would notice a significant performance difference.
- mwsherman 13y agoMight be interesting to compare to F#‘s units of measure: http://msdn.microsoft.com/en-us/library/dd233243.aspx http://msdn.microsoft.com/en-us/library/dd233243.aspx
- eloff 13y agoDid 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.
- zastrowm 13y agoI 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.
- eps 13y agoIf you are "doing conversions all over the place", you are looking at a fundamentally flawed code design that costs you performance. Ensuring consistency of coversions here is like painting a house whose roof's on fire. C/F example is synthetic, but it doesn't make it any good - if you are operating with data that can be expressed in different units, then pick a single unit, stick with it throught the code and convert only when displaying (or passing to external party code).
- deleted 13y ago[deleted]
- pdpi 13y agoOr perhaps you're integrating a large number of systems, each with its own conventions.
- wvenable 13y agoWhat is missing the rest of the program that could be using temperature hundreds of times scattered throughout the calculations and UI. A small type hierarchy provides type safety for all that other code and is certainly less prone to errors than well named variables and simple conversion functions. The example is bit too trivial as the hierarchy doesn't provide much value. I would assume the base class would actually provide some useful functionality that isn't in this example or it could be eliminated entirely.
- midas007 13y agoStatic type checking is a double-ledger bookkeeping of "what I am/have is what you are/want." Erlang and Haskell have the most interesting approaches to typing: optional and extensible respectively.
- taeric 13y agoI 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 to partially proving it. Laudable, to be sure, but anyone that has attempted to prove that quick sort works knows, it isn't easy. Just understanding the shorter versions of some programs takes a fair bit of brain power as it is. Understanding the fully specified version can be orders of magnitude more complicated. So, I am left wondering if it isn't enough to stop at "passes all the tests." Especially if you have the correct tests. And eventually one wonders whether the "correct tests" includes more stringent types. Maybe it does. Maybe it doesn't. Likely depends on the situation, is my bet.
- moron4hire 13y agoType 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.
- taeric 13y agoI don't think you really wrote anything I did not acknowledge. Yes, types are a form of complete test over the shape/type of data that they encode. If you can encode your data with the correct type, it will go a long way to making sure you don't have errors in the types. However, I take issue with this statement: "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. " For the simple cases, I agree with this. But when you see the type gymnastics that some programs go through to guarantee some traits, it is hard to agree with any claim of "easier to write." Consider a "true" quicksort in haskel[1]. My assertion was that the "hard part" of programming is often in the logic, not necessarily the shape/type of the data. When you see attempts at getting the logic of a program into the type system, things start to take a turn for the worse. Really worse when you find you have many types that all encode the same type of data. (Consider the metric system debate.) My point about "having the right tests" is that sometimes you only care/have the knowledge to think about/whatever certain scenarios that are either likely or guaranteed to happen. Pushing everything in the type system implies that you had the energy to think of and consider everything. My assertion is often you don't have that time/energy. So, sure, you will have the "right tests" in that scenario to an extent, since you will have all tests. However, I wonder if most programs couldn't work out with a much smaller subset of those tests. A direct analogy for the point I am making is physics. Classical/Newtonian physics break down and don't work at a level. Worse, they rely on a lot of simplifications and actually describe what is happening moreso than how/why. Yet, for a large portion of calculations that people will do, they work. I grant that encoding all of the assumptions into the calculations will make them more accurate, but often you don't need that level of accuracy. And they definitely make them harder to understand. [1] http://www.haskell.org/haskellwiki/Introduction/Direct_Translation http://www.haskell.org/haskellwiki/Introduction/Direct_Trans...
- jeffdavis 13y ago"'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 mutators? For an Ellipse, it may have a mutator called squash() that holds the major axis constant and halves the minor axis. But that leaves us with a problem, because circles can't be squashed while remaining circles. Let's say we have a program like: void f1() { Circle myCircle(10.0); f2(myCircle); cout << myCircle.getRadius() << "\n"; } void f2(Ellipse &e) { if (...) e.squash(); } Inheritance says that f2 must also accept a Circle. But what happens when we try to squash it? We can either throw a runtime error there, or we could let the squash succeed and the getRadius() in f1() will fail. Although a Circle value is an Ellipse value, a Circle variable is not an Ellipse variable. In fact, inheritance for variables should go in the opposite direction as inheritance for values, because an Ellipse variable can certainly hold a Circle value.
- hrjet 13y agoThis 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.
- dragonwriter 13y ago"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.
- minikomi 13y agoSome 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/mnId0uxRaTU https://groups.google.com/forum/#!topic/racket-users/mnId0ux... [2] https://github.com/Metaxal/measures#4-dimensions-and-contracts https://github.com/Metaxal/measures#4-dimensions-and-contrac... [3] http://futureboy.us/frinkdocs/#SampleCalculations http://futureboy.us/frinkdocs/#SampleCalculations
- jeffdavis 13y agoDoes 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?
- tel 13y agoThere's Cloud Haskell which is an attempt to do Erlang style distributed concurrency in Haskell. For non-distributed concurrency, you can already get away with using Haskell's green threads and channels. Furthermore, there's the beginning of a hot code swapping system in GHC now, but it's not terrifically popular at the moment---it was built for a particular use at Facebook and hasn't gotten too far from there. It's worth talking about why Cloud Haskell is tough, though. The primary issue is that Haskell doesn't have any kind of transparency into functions---if you are given a type of `(a -> b)` there's no way to examine what it is on the inside, no concept of source that could be sent to another node. This limits the kinds of messages and spawning that can happen in a distributed Haskell system. The Cloud Haskell project is working to remove as much of this limitation as possible, though.
- carlob 13y agoIt 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!
- NigelTufnel 13y agoMaybe it's just something I don't understand but I would go with Celsius everywhere with converting to Fahrenheit/Kelvin when showing/grabbing temperature to/from user.
- ygra 13y agoThe Celsius/Fahrenheit example isn't the best. Generally all examples based on units of measurement are probably flawed because sane systems can just standardise on SI and be done with it. However, different coordinate systems that should never be mixed are useful to keep separate. At work we deal with maps and geographic information, so there are always at least three coordinate systems: Screen, projection and geographic coordinates. Having a type system that catches mistakes there would be so much nicer than just always passing P2D around and trying to never mix different ones.
- pointernil 13y agoI often wonder how the "tool" named MONADS could help to deal with this quite old problem of what I call meta-types or semantic types (i'm sure there is a proper name for this; I'm not a cs/type-theory professional ;) Time handling f.e. and often physical Units would quite often benefit from much stronger guaranties at compile and runtime. And no, OOP and it's tools are not sufficient to handle this... in theory maybe, not in day to day development. We need to translate the core ideas of a monad into more a "mainstream" syntax and runtime env. to prevent the next incident of: I'll just add the integer 365 to my certificate valid-date within our core cloud infrastructure code... I'm not necessarily a MS fan, but would love to see how Anders Hejlsberg f.e. would tackle this kind of language-design challenge ;)
- tel 13y agoI don't think this has much at all to do with monads.
- pointernil 13y agoCare to explain in a little more detail? Is a calender resp. date-time monad really such a far fetched idea?
- tel 13y agoThat's just a state monad, the thread and what I assumed and may have assumed incorrectly you were talking about, was around overloading math operations to work on dimensionalized quantities like feet and seconds and the like. You can dimensionalize time as well—take a look at the time or thyme Haskell packages for some fascinating examples. Doing computation in the context of the current time is absolutely monadic. If you're handling time abstractly, it's the state monad. If you're handling it for real then you need some kind of monad protecting communication with NTP/your local CPU clock. That's IO in Haskell.
- pointernil 13y agoYup, you assumed incorrectly. I can see the qualitative difference between operator overloading and monads. That's why I suggested to transfer the core idea of monads into more mainstream languages to provide more "semantic" safety, so to say. Thanks for the clarification.
- SuddsMcDuff 13y agoThe best write up I ever heard for this approach (often called "primitive obsession" - http://sourcemaking.com/refactoring/primitive-obsession http://sourcemaking.com/refactoring/primitive-obsession) was in Object Calisthenics, an essay by Jeff Bay in The Thoughtworks Anthology (http://www.amazon.co.uk/The-ThoughtWorks-Anthology-Technology-Programmers/dp/193435614X http://www.amazon.co.uk/The-ThoughtWorks-Anthology-Technolog...): "an int on its own is just a scalar, so it has no meaning. When a method takes an int as a parameter, the method name needs to do all of the work of expressing the intent. If the same method takes an Hour as a parameter, it’s much easier to see what’s going on. Small objects like this can make programs more maintainable, since it isn’t possible to pass a Year to a method that takes an Hour parameter. With a primitive variable the compiler can’t help you write semantically correct programs. With an object, even a small one, you are giving both the compiler and the programmer additional info about what the value is and why it is being used. Small objects like Hour or Money also give us an obvious place to put behavior that would otherwise have been littered around other classes."
- dschiptsov 13y agoLooks rather like a type-system abuse. btw, there is Haskell for that.)
- alipang 13y agoHe claims it's to make wrong code look wrong. He uses the type system in order to prevent bugs, how is that abuse? Is the type system only there for performance in your opinion, or what's your angle?
- ternaryoperator 13y agoWhat he's talking about is avoiding primitive obsession. Always good advice, although I find its greatest benefit comes in typing collections, rather than just using primitives like List or ArrayList.