Hacker News new | comments | show | ask | jobs | submit login

Here I've translated the example to haskell:

    data Exchange = Exchange { bic :: String, name :: String }
    data Security = Security { description :: String, exchange :: Exchange, isin :: String }
    data Stock = Stock { security :: Security }
    data Bond = Bond { security :: Security, expiry :: EpochTime }
    data Trade = Trade { price :: Decimal, quantity :: Decimal, security :: Security }
Now to add Option:

    data Option = Option { security :: Security, call :: Bool, lotSize :: Decimal, maturity :: EpochTime, strikePrice :: Decimal }
And in the example, the problem is that the Option uses Security, which has an isin, which doesn't make sense for Option. In haskell, this is a sort of problem which is typically fixed by adjusting the data types. There are many ways they could be changed, some will model the domain better than others. Let's just make the same quick fix used in the example, of allowing isin to not be set:

    data Security = Security { description :: String, exchange :: Exchange, isin :: Maybe String }
This means that isin is Nothing or Just a String. As soon as this change is made, every place in the program that directly accessed the isin will fail to compile. Fixing the compilation errors will involve adding a case to handle isin-less Securities.

    - foo (Security { isin = i }) = 
    + foo (Security { isin = Just i }) = ...
    + foo (Security { isin = Nothing }) = ...
The code does become somewhat ugly with these cases, but you know every case has been covered, and that it will work.

Maybe later it's decided to go back and fix it to use the separation between physical and derivative securities that was originally considered but not done due to lack of time. It could then look like this:

    data Security = PhysicalSecurity { description :: String, exchange :: Exchange, isin :: String } 
                  | DerivativeSecurity { description :: String, exchange :: Exchange }
Again this type change would drive a pass through the code, fixing it up to compile.

    foo (PhysicalSecurity { isin = i }) = ...
    foo (DerivativeSecurity {}) = ...
Again you'll know when you're done because the program will successfully compile. In this case, splitting the data type seems to have led to better, clearer code. It might be worthwhile to factor out a helper type to simplify the Security type:

    data SecurityBase = SecurityBase { description :: String, exchange :: Exchange }
    data Security = PhysicalSecurity { base :: SecurityBase, isin :: String }
                  | DerivativeSecurity { base :: SecurityBase }
Although you may find this complicates other things as you "follow the types" and change the code to match. There are surely other approaches; so far this has stuck with simple data types, but typeclasses could also be used. You may want to constrain Bonds to using a PhysicalSecurity, and Options to using a DerivativeSecurity, and there are various ways that could be enforced. And so on.

What was surprising to me coming to haskell from a background in loosely typed languages (and lowlevel langs like C) is that the types are not a straightjacket that is set in stone from the start, but ebb and flow as you refine your understanding of the problem domain. What well chosen types in haskell do constrain is the mistakes you want to be prevented from making. These days if I find myself repeatedly making a mistake in my code, I adjust the types to prevent that sort of mistake in the future.

---

Side note: The above code will not compile as written, because it exposes an annoying problem in haskell's record syntax. There are several fields named "security" that conflict with one-another. This is typically dealt with by using ugly field names (stockSecurity, bondSecurity, tradeSecurity, optionSecurity), or more advanced things like lenses, or by putting the data types in separate modules and using module namespacing.




The original blog post wonders whether function programming will give a solution to the problem using immutability and lack / tagging of side-effects. Haskell is a purely functional language, but the feature of the type system, algebraic data types, that actually helps solve the problem here would be perfectly feasible to add to a language like C.

It reminds me of other features, like garbage collection, that were initially introduced in languages like Lisp, but turn out to be useful in the wider world.


I find modules namespacing to be a more elegant solution than prefixed field names. (I haven't played around with lenses for this kind of use, enough.) It's just a shame that Haskell doesn't have an in-file syntax for modules like, say, OCaml but each module needs to put into a separate file.


I do too. I also tend to find that once I have a module for a particular record type, I eventually find enough other things to put in it, like instance declarations, that it's not a hardship to have a separate file.


Yes, it's not a hardship, but it does raise the threshold for doing the Right Thing. Quite unnecessarily in my humble opinion, since OCaml provides an example of how to add this feature to the language without colliding with any of Haskell established concepts. (I don't know about collision with the existing syntax, but you'd enable the modules like any other extention with a pragma.)

Do you know whether Template Haskell could be contorted to provide a syntax?




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: