"Making Impossible States Impossible" by Richard Feldman

By: elm-conf

335   0   13797

Uploaded on 09/19/2016

Among the most time-consuming bugs to track down are the ones where we look at our application state and say "this shouldn’t be possible."

We can use Elm’s compiler to rule out many of these bugs in the first place—but only if we design our Models using the right techniques! This talk explores how.

Comments (6):

By aczerepinski    2017-09-21

There's a great talk on this subject by Richard Feldman from Elm Conf. I haven't used typescript so I'm not sure if the techniques translate well. https://www.youtube.com/watch?v=IcgmSRJHu_8

Original Thread

By jon49    2017-10-08

Languages like F# give a nice sweet spot between static typing and dynamic typing. It has Type Providers that "generate" code on the fly as you are typing. You don't need to specify all the types, it will infer many types for you. So, you almost feel like you are writing in a dynamic language but you it tells you if you are writing something incorrectly.

I would not consider a language to be modern unless it has Type Providers I consider this to be such an essential feature. I believe Idris and F# are the only languages that have it. People are trying to push TypeScript to add it - who knows if it will happen.

Many are saying that if you have a dynamic language you just need to be disciplined and write many tests. With good static typed languages like F# you can't even write tests on certain business logic since the way you write your code you make "impossible states impossible", see https://www.youtube.com/watch?v=IcgmSRJHu_8

Original Thread

By anonymous    2017-09-20

Short answer

Option fields have use cases; they're not intrinsically bad. However, even though several well established libraries (e.g. ScalaTest) define classes with Option fields, the latter, IMO, tend to be a code smell, as they often try to do too much for their own good.

In many cases, a type containing optional fields can easily and advantageously be replaced by an algebraic data type.

An example

The domain

Consider a business domain dealing with accounts. An account starts its life one day as an open account, but may eventually be closed. Accounts, among other data, contains the dates on which they were open and closed, where applicable.

Using an Option field

Here is an implementation of an account, using an Option field:

final case class Account(openOn: LocalDate, closedOn: Option[LocalDate], ...)

We also have an account service, which defines, among other things, a close method:

trait AccountService {
  // ...
  def close(account: Account): Account

This approach is problematic, for a number of reasons. One problem is that Account isn't particularly performant: because closedOn is a "boxed" type, you have one level of indirection too many, so to speak. Moreover, Account's memory footprint is less than ideal: a "closed account" contains a pretty uninteresting value (None), which is a waste of space.

Another, more serious, problem is that the close method cannot enforce, at the type level, that the parameter be an "open account" and the result be a "closed account". You would have to write tests to check that this business rule is enforced by your implementation.

Using a small ADT (and eschewing Option fields)

Consider the following alternative design:

sealed trait Account { ... }

final case class OpenAccount(openOn: LocalDate, ...) extends Account

final case class ClosedAccount(openOn: LocalDate, closedOn: LocalDate, ...) extends Account

This small ADT remedies the performance problem, but there is more... You can now encode the business rule at the type level! This is an example of making illegal states unrepresentable. As a result, your service's API becomes more expressive and harder to misuse:

trait AccountService {
  // ...
  def close(account: OpenAccount): ClosedAccount

This example may be sufficient to convince you that the second approach is preferable, and that Option fields are best avoided (or, at least, used sparingly).


For more more about eliminating optional fields towards making illegal states unrepresentable, see

Original Thread

By anonymous    2017-09-20

If your record will only ever have twelve elements and you don't mind sacrificing a little verbosity in your type definition for type safety (make impossible states impossible!), you could start with enumeration of those twelve indexes:

type Index
    = Inner1
    | Inner2
    | Inner12

You could redefine Record to a type instead of a record type alias:

type Record =
        -- repeat InnerType 12 times

Now you can create a few convenience functions for getting and setting values in a type-safe manner:

get : Index -> Record -> InnerType
get i (Record i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12) =
    case i of
        Inner1 -> i1
        Inner2 -> i2
        Inner12 -> i12

set : Index -> InnerType -> Record -> Record
set i val (Record i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12) =
    case i of
        Inner1 -> Record val i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12
        Inner2 -> Record i1 val i3 i4 i5 i6 i7 i8 i9 i10 i11 i12
        Inner12 -> Record i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 val

And you can create a list from your record like this:

toList : Record -> List InnerType
toList (Record i1 i2 i3 i4 i5 i6 i7 i8 i9 i10 i11 i12) =
    [ i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12 ]

As you can see, this gets verbose, but it's only in the definition of the Record type that it becomes verbose. If you confine all this code to a Record module, then accessing and modifying records is clean, concise, and more importantly, type safe. You are not subject to a Dictionary where a key may not exist, or a List or Array where an index might not exist. Your Record type would be sealed up nice and tight, at the expense of a little verbosity in the definition.

Here is a gist containing the full definition.

Original Thread

Submit Your Video

If you have some great dev videos to share, please fill out this form.