The author of this series also did this interesting conference talk on the same subject: https://vimeo.com/97507575
But has anyone here seen more about this “designing with types” approach to programming? (In any language.)
It sounds very enticing, but I have no experience with it, so can’t tell if this is much pragmatically much different than just using OO classes as types.
For one example in Haskell of designing with and thinking in terms of types: http://bitemyapp.com/posts/2014-11-19-update-map-in-haskell.html
You can’t really model domains properly without a type system that is expressive. This stuff goes a long way to making ordinary code a lot more palatable and maintainable.
I have some very incomplete notes here: https://github.com/bitemyapp/presentations/blob/master/modeling_data_in_haskell/modeling_data.md
Here’s a fun example, never write an evaluator/fold for a datatype yourself ever again: http://hackage.haskell.org/package/catamorphism
The epiphany here is that functions that “consume” or break down structure can be generated from the definition of the structure. You define the domain - it generates the code. Wrap according to convenience.
For “make illegal states unrepresentable”, I have a series of examples here: http://bitemyapp.com/posts/2014-11-18-strong-types-and-testing.html
For a simpler'ish example of deferred definition of data (parameterizing), see: http://bitemyapp.com/posts/2014-04-11-aeson-and-user-created-types.html
Pulling out types so you can specify sub-components: http://bitemyapp.com/posts/2014-04-05-grokking-sums-and-constructors.html
Parsing JSON that can vary in structure: http://bitemyapp.com/posts/2014-04-17-parsing-nondeterministic-data-with-aeson-and-sum-types.html
If you don’t mind some humor, this is a post translating a mini-project example that was originally in Python: http://bitemyapp.com/posts/2014-12-03-why-are-types-useful.html
I also wrote a HowIStart article for setting up a small Cabal project to parse some CSV data in Haskell: https://howistart.org/posts/haskell/1
I’m working on a book to make Haskell approachable for working programmers and non-programmers (we test the book with people that have never programmed before) at: http://haskellbook.com/
Hoping the book will, if nothing else, dispel the idea that we need to use less powerful languages that seem more familiar because learning materials haven’t caught up.
Solid post, great links.
I’ve been moving this direction with Ruby. It’s been partly inspired by learning Haskell and partly by trying to decompose and organize my objects. As the author says:
Many of the suggestions are also feasable in C# or Java, but the lightweight nature of F# types means that it is much more likely that we will do this kind of refactoring.
There’s also a resistance to it in what’s considered “idiomatic”. It sounds small and silly, but I think it may be the one-class-per-file rule. When I want a User’s email_address attribute to be an EmailAddress, even before I make it immutable and add validations, I have a file named email_address.rb I have to go look at. That inconveniences everyone a little and there’s not much code there.
And then, worse, to match the basic guarantees of the F# example in part 2 with immutability and validity, off the top of my head it’s going to look something like this:
class InvalidEmailAddress < ArgumentError ; end
def_delegators :@address, :length, :to_s, :to_str, :<=>
def initialize a
@address = a
raise InvalidEmailAddress unless valid?
address =~ /\A\S+@\S+\.\S+\z/
And that’s a simple type with one field. I could metaprogram away almost all of this (I’ve seen a lot of one-field types with this shape now), but it already looks unfamiliar to Ruby developers and depends on two gems. It’s teetering on the edge of idiomatic.
There’s a lot of value to be had, though. The pieces of the system are more reliable. There’s now one explicit place to say what it means for an email address to be valid or how to work with it - I really like email address as an example because in most apps I’ll see it on many models, that knowledge is just repeated and smeared across the system. It’s so common that devs have a hard time recognizing it until it grows out of control.
For a slightly larger example, check out AuthenticationResponse from my TwoFactorAuth gem. The gem authenticates users by sending a challenge AuthenticationRequest to the client and confirming that the AuthenticationResponse can be decomposed and its signature verified against the AuthenticationRequest. Most of the U2F libraries do this by passing around strings for the fixed-length records; TwoFactorAuth decomposes them into types (implemented as Ruby classes) that each know how to validate themselves.
I bottom out in the AuthenticationResponse by not making types for the fields (bitfield, user_presence, counter, signature), but those would’ve been too much of the above boilerplate for a very small benefit in the one or two places they’re used. This librarby has the benefit of being nearly feature-complete and “done” to the spec that app code will never have. There, the decision that it’s not worth it now usually means difficult-to-recognize pain as the app grows and changes and becomes “primitive obsessed”.
So to address your question of whether it’s pragmatically different: absolutely. It’s more up-front code + training up-front for a harder-to-express long-term benefit. In functional languages like F# and Haskell this is the default and enables a lot of compiler-assisted refactoring. I’m not just saying “well, it’s harder with OO classes”, it’s unidiomatic and I see those human considerations looming larger than technical considerations.
Wow. Awesome. Thanks Peter. Great thoughts.
I have been experimenting and obsessing about this kind of hybridization of FP and OO for a year, so you gave me a great setup line. Next, ask me who’s on first. :)
I’d say pragmatically it is far, far different from using OO classes as types. Theoretically, it’s possible to do it with OO classes, but there are a number of reasons why they are both too heavyweight semantically and syntactically to make it likely.
The core typed FP languages today have this concept in spades. Learn OCaml, Haskell, F#, the functional parts of Scala and you will see it literally everywhere.
I don’t have any experience with F#, but I know that this is a common pattern in C++/Java, make everything private, restrict access through Get/Set functions, the Set functions can do validation and fix or reject data too.
I only read the first 3 o 4 posts in the series and then got bored, so it is highly possible that something tricker and more interesting happens later.