Skip to main content

Conferences

Explaining good software design, especially with functional programming (e.g. Elm).

  • Domain Modeling Made Functional | Scott Wlaschin

    Notes
    • How can this be improved?

      type Contact = {
      FirstName: string
      MiddleInitial: string
      LastName: string

      EmailAddress: string
      IsEmailVerified: bool
      }
    • A data structure that represents design choice:

      type String50 = String 50 of string

      let createString50 (s: string) =
      if s.Length <= 50
      then Some (String50 s)
      else None

      createString50:
      string -> String50 option

      or:

      let createEmailAddress (s: string) =
      if s.Contains "@"
      then Some (EmailAddress s)
      else None

      createEmailAddress:
      string -> EmailAddress option
    • Union types:

      type VerifiedEmail = VerifiedEmail of EmailAddress
      type VertificationService =
      (EmailAddress * Verificationhash) -> VerifiedEmail option

      type EmailContactInfo =
      | Unverified of EmailAddress
      | Verified of EmailAddress
    • A data structure that represents a business rule:

      type Contact = {
      Name: PersonalName
      Email: EmailContactInfo
      }

      type PersonalName = {
      FirstName: String50
      MiddleInitial: String50 option
      LastName: String50
      }
  • Make Impossible States Impossible | Richard Feldman

    Notes

    Make impossible states impossible

    We need to restore a recently deleted input
    • Bad design:

      type Model =
      { status: Maybe String
      , questionToRestore: Maybe SurveyQuestion
      }
    • Happy path:

      { status = Just "Question deleted"
      , questionToRestore = Just someQuestion
      }

      { status = Nothing
      , questionToRestore = Nothing
      }
    • Impossible path but possible in code:

      { status = Nothing
      , questionToRestore = Just someQuestion
      }

      { status = Just "Question deleted"
      , questionToRestore = Nothing
      }
    • Proper solution: union types

      type Status
      = NoStatus
      | DeletedStatus String Question
    Zip list: maintaining a history (go back, current, next)
    • Bad design:

      type History =
      { questions: List Question
      , current: Question
      }
    • Happy path:

      { questions = [q1, q2, q3]
      , current = q2
      }
    • Impossible path but possible in code:

      { questions = []
      , current = q1
      }
    • Better solution: zip list, two lists with 1 element. Impossible to have a non-existing element.

      type History =
      { previous: List Question
      , current: Question
      , remaining: List Question
      }

      -- example
      { previous: [q1, q2]
      , current: q3
      , remaining: [q4, q5]
      }
    • No break changes:
      • Implement a zip list internally in a module and expose an interface for other modules
      • Other modules can only interact with back and forward functions
      • The internal implementation can change without breaking the interface
  • Convergent Evolution | Evan Czaplicki

    Notes
    • Convergent Evolution: people working independently converge to similar solutions without prior knowledge of each other
      • Flying seems a neat idea, so bees and birds both have wings
      • But bees and birds have very different "implementation details"
      • The key is not to determine which one "implemented" flying better but to consider how the design of wings fit into other "features" of the design
    • In Elm and React, both similarly have virtual DOM but implemented and look very differently
    • JavaScript has C-style syntax while Elm has ML-style syntax doesn't matter. The thing is how well this decision fit with other features. ML-style syntax fits well with Elm emphasizing the immutability feature.
    • Elm architecture vs Flux in React:
      • Elm architecture: Model passes HTML to runtime, runtime passes messages back to the model, then loops
      • Both reached an MVC pattern, following a uni-directional flow
    • Other properties:
      • Immutability: lazy in Elm
      • Static analysis: linter or TypeScript in React, hint messages at compile time in Elm
    • It's not right or wrong choosing which stack, the key is how the feature sets work together
    • The overlapping ideas of these stacks, despite being implemented differently, are the ones that seem to be good ideas
  • The Life of a File | Evan Czaplicki

    Notes

    How does he grow Elm code? What happens when the file is "too long"?

    • Life of a file: start small and grow, eventually how a file grow until it splits into two files
    • JavaScript knowledge:
      • Prefer shorter files: seems like shorter is better because it avoids sneaky mutations when files get larger
      • Get the architecture right from the start: refactoring is very risky and a full rewrite is easier
    • Elm perspective:
      • It is not possible to share variables and states, sneaky mutation is 0%
      • Refactoring is cheap and reliable with static types and static analysis
      • The way Elm is designed changes how you grow a file or even the codebase

    The idea is to build modules around data structures, not the length of the file.



    • Things to consider when choosing the data structures:
      • Static type analysis? (spelling of keys should be checked)
      • Does ordering matter? (objects or lists)
      • Union types to limit the possible states or require the ability to dynamically update the list of possibilities?
    • Breakout functions into modules when they start to build around data structure or specific functionalities for a domain in the business model:
      • And within a module, the implementation details (the data structure) should not leak
      • Only expose a limited interface, but no less
      • Feature changes are to extend/limit the interface the module exposes
      • Testing on the API, rather than every usage
      • Refactoring within the module is easier because the API won’t change
      • Modules can also maintain invariant (e.g. only two fruits), rules that cannot be enforced through data structure only
    • Bad practices
      • Get/set: the purpose of modules is to hide details, but setters will expose details. Instead, try to expose as little as possible but no less
      • Don’t overdo it: only extract modules when there is a problem. Don’t do premature refactoring.
      • Don’t try to make modules because something is similar, focus on the data structure. Only do it when things are related.