Last active
August 19, 2025 15:19
-
-
Save rupertlssmith/c41f8798ed7c19b6c8edc5361725303d to your computer and use it in GitHub Desktop.
Revisions
-
rupertlssmith revised this gist
Aug 19, 2025 . 2 changed files with 46 additions and 46 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -24,52 +24,6 @@ SOLID is a mnemonic acronym for a group of five good rules of thumb in computer Is SOLID the right rule set for Elm, or do we need a different set of principles for functional programming? To answer that question I have come up with a set of design patterns for Elm packages and applications and tagged them with any SOLID rules that they follow. ## Writing Ideas... Just dumping some thoughts here to tidy up later. Elm compiling to javascript means it runs as single threaded event driven programming. with immutable data structures. This means that it is safe to share state over your entire application. This in turn means that building applications in Elm is best done as 'flat' as possible. The reason why? Otherwise state will be encapsulated to a module and you may get into the anti-pattern of having modules send events to other modules to update or query state. Share state between modules where needed as single threaded and immutable cancels out the difficulties this would pose in multi-threaded object oriented languages. @@ -87,3 +41,7 @@ State machines are your best friends in Elm. This can give Elm a feel somewhat s Top.elm or Main.elm exists to wire together your application. Ideally as a functional program that composes its parts together by function composition, and not as a routing bus in an actor model system. One of the reasons Actor model is a pain in Elm is that the Event type needs to be known everywhere or translated and this always causes either close coupling in the application or elaborate efforts to avoid that with translation and then a maintenance headache to manage it all. This way is also a form of defunctionalization. Elm is a language without minimal syntax, but there is a high degree of orthogonaility in that syntax that allows you to combine in increasingly powerful ways. But always in Elm that power comes with a complexity cost. The effort/reward/complexity/risk scale therefore lines up quite well in Elm. Only pay for the reward or complexity or risk that you really need with your available effort. So I made a list of steps of increasing effort up to more powerful techniques and arranged them in order. At the base are the simpler and more used techniques, and as you get higher, you get more complex and expensive techniques that should also be used less. This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -607,3 +607,45 @@ square s = ``` --- ### My original write ups on Design Patterns * Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. * Smallest Type - Keep your types as small as possible. This mainly applies to records in Elm. You can have a function over a record that does not use all fields in the record and it is good practice to replace this with an extensible record that only reveals the actual fields used. Avoid stringly typed things that can be described by more specific types and use custom types. (ISP) * Higher Order Functions - The most common ones you will encounter are `map` and `andThen` both of these take functions as arguments, apply them so some kind of wrapped entity, list elements, a `Cmd a` or `Html a`, or a `Decoder a` and so on. This is a form of dependency injection, where you supply a "callback function" that provides custom behaviour over some regular and re-usable construct in the language. This is a big part of what makes functional programming fun and powerful! Functions are first class entities in the language and can be passed around like any other value. (DIP) * Opaque Types - You can hide the constructors of a custom type so they are not exposed by a Module. This is a great way of exposing an API over some type but without exposing the implementation can lead to lower coupling and better separation of concerns in your design. (ISP) * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. (SRP) * Ports are OK - Elm programmers sometimes get a bit hung up on ports. Certainly doing an synchronous call via ports is a bit inconvenient and we wish we had task ports for this. But it isn't a big deal and often leads to cleanly organized code so just use ports when you need to. * Nested TEA - The Elm Architecture is Elms Model-Update-View pattern. Avoid making everything a TEA-like component with its own encapsulated model. Everyone tries this at least once. The only tim eyou really need to do this is if you have N things on a page. For example, if I have a list of N items which are custom elements of my UI and create side effects, I will need each of them to have its own encapsulated `Model` and `update` and `view`. For single use constructs work on breaking out update-like functions or view-like functions into modules instead of complete TEA-like components. See "Smallest Types". * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. * Parse don't Validate - If you validate user input but do not parse it, when you later write a function over it, your function will still have to deal with illegal states. For example, user inputs a number as text. My function adds their input to some value. If the input to the add function is a String, I must still consider cases where it is not a number, even though I earlier validated it. If I parsed it into an Int at validation time instead, I can now safely add it without considering the illegal state which was entirely eliminated as the data crossed over into my domain model from the IO controller. Keep the raw String too, as you may still need to show it to the user for error reporting purposes. * State Machines - Elm and State machines go together like coffee and cigarettes. A state machine is a great way to design a UI with states corresponding to visual states the user will see and interact with, as well as some tranisitory states such as "waiting for data" that are needed to ensure the UI correctly progresses through legal state transitions only and can recover from errors such as "fetching data failed". An Elm program is single threaded and sequential. The real world is parallel. A state machine can marshall real world events into a sequential ordering. Consider the coca cola vending machine example (irn bru machine in Scotland) which needs some internal states to prevent the user pressing the "vend" and "coin return" buttons at the same time and getting a free drink. * Style Guide CSS - Just as with HTML/CSS it is usually beneficial to separate content and style. The reason? So that style can evolve independantly and so that style is a thing in itself with its own specialists. If style is buried in application view logic, it can become harder to change. A clean separation is usually achieved by making use of good old CSS classes. You can describe your CSS classes with a custom type and get the benefit of typechecking so that you never typo a class name again. * Style Guide elm-ui - Even without the common CSS-as-style approach, elm-ui is a good choice for Elm also. It is fast and powerful. Still make a style guide out of your styling code and maintain a stlye/content split. You can mix both elm-ui and CSS too, use elm-ui more for layout purposes and stick with CSS for the style. * Web Components - In addition to ports you can also interact with web compoments. If you need something custom in your DOM and Elm is a pain to do it with, consider a web component and how you will use its attributes, properties and events to communicate with your Elm code. Some powerful things can be achieved this way, such as a WYSIWYG text editor in content editable mostly written in Elm. * Defunctionalization - This is when functions return data types describing the partially evaluated output of the function, instead of having a total function that outputs its fully evaluated result. For example, in a calulator application, instead of evaluating down to a number as the user enters each operation, you could gather the operations into a syntax tree, with ops as nodes and numbers as leafs. The advantage is flexibility - you could display the syntax tree, or you could optimize the syntax tree before evaluation and so on. Note that `Msg` in Elm is a form of defunctionalization. The "out message" pattern in Elm is also a form of defuctionalization and on that makes update-like functions using it, less total. An update with an out message is saying that the update function could not complete all its work and needs to return a partial evaluation for some other function to complete - but what is the advantage obtained for the extra cost here? Use defunctionalization when you have a clear use case for doing so, as with all the techniques described here. * Actor Model Fetish - You naughty Elixir or Erlang programmer! But seriously the actor model is a great pattern, and Erlang concurrency is amongst the safest in no small part due to following that model. It is also inevitable that you will encounter this pattern in Elm. It is fine to use it so long as you are aware of the pitfalls. The danger is that the routing of events between modules becomes hard to understand and grows into a maintenance cost for an application. If this happens, then simple things like accessing or updating fields in your application model become a hassle and kills development speed. Also when you communicate with Msgs as events in a state machine, the more Msgs you have, the more complex your state machine needs to be. This creates more surface area for undesirable state to accumulate and can make application logic more complex than it really needs to be. Note that actor model with "out messages" is a form of Defunctionalization. Your update functions become partial functions! * IO Monad Fetish - The true fetish of functional programmers. We can push all that unpredictable side effecty stuff to one side. Side effects in Elm as Cmd or your own Effect type are forms of Defunctionalization. You can keep this to miniumum by only having Msgs for side effects, and keeping Msg opaque outside of a component of the application - it exists purely for the processing of side effects that are entirely internal to the module. Instead of encapsulating data fields like in object oriented programming, we want to encapsulate side effects when extracting code as a component in Elm. * Poor Mans Typeclass - The type signature of a record of functions can act as an "interface" that describes a type class in Elm. You can use such interfaces to describe the behaviour of objects in a way that leaves the implementation of the object abstract. The problem in Elm is that when you make such records recursive (by wrapping in a custom types or recursive over a type parameter) you will find that the type of the implementation of the object will naturally pop out as a type parameter. As a result the poor mans typeclass does is not drop-in Liskov substitutable and not quite a realisation of the Open-Closed principle; since different implementations alter the type of the surrounding program. Useful when you want higher order functions that allow many function parameters to be injected, as the use of a record lets you name them. Very useful for implementing dependency injection between modules as function composition. * Create Your Own Effects - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. * Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to. (OCP, LSP) -
rupertlssmith revised this gist
Aug 19, 2025 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,7 @@ A draft write up of the design patterns by ChatGPT. **Note:** The example code is mostly not in a good state yet, still a work-in-progress! # Elm Design Patterns and Philosophy **A curated collection of Elm design patterns mapped to SOLID principles** *Compiled on August 19, 2025* -
rupertlssmith revised this gist
Aug 19, 2025 . 1 changed file with 469 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,29 @@ # Elm Design Patterns and Philosophy **A curated collection of Elm design patterns mapped to SOLID principles** *Compiled on August 19, 2025* ## Table of Contents - [Life of a File](#life-of-a-file) - [Smallest Type](#smallest-type) - [Higher Order Functions](#higher-order-functions) - [Opaque Types](#opaque-types) - [Know how to Name Modules](#know-how-to-name-modules) - [Modules, Modules, Modules](#modules,-modules,-modules) - [Ports are OK](#ports-are-ok) - [Nested TEA](#nested-tea) - [Domain Modelling](#domain-modelling) - [Parse don't Validate](#parse-don't-validate) - [State Machines](#state-machines) - [Style Guide CSS](#style-guide-css) - [Style Guide elm-ui](#style-guide-elm-ui) - [Web Components](#web-components) - [Defunctionalization](#defunctionalization) - [Actor Model Fetish](#actor-model-fetish) - [IO Monad Fetish](#io-monad-fetish) - [Poor Man’s Typeclass](#poor-man’s-typeclass) - [Create Your Own Effects](#create-your-own-effects) - [Object Oriented Elm](#object-oriented-elm) ## Life of a File ### When to Use @@ -134,3 +160,446 @@ toString (UserId str) = str ``` --- ## Know how to Name Modules ### When to Use Use this when your project is growing beyond a single file and you’re splitting out parts of the application. ### Full Description Elm does not require module names like `Update.elm` or `View.elm`, and you should avoid generic names. Instead, name your modules after the domain concepts they contain. Update-like functions should describe what they update (e.g. `updateUser`, `applySettings`). View-like modules should be named visually (e.g. `Page.Home`, `Component.Navbar`) to reflect their rendered output. This creates better correspondence between code structure and application behavior. ### (SOLID Principles) Single Responsibility Principle (SRP), Interface Segregation Principle (ISP) ### Elm Code Example ```elm -- Instead of View.elm and Update.elm, prefer: -- Page/Home.elm with: view : Model -> Html Msg -- User/Update.elm with: updateUser : Msg -> Model -> (Model, Cmd Msg) ``` --- ## Modules, Modules, Modules ### When to Use Use when breaking out application logic into separate files and components. ### Full Description Design your modules around principal types. Each module should center on one key type, and its name should reflect that type. This promotes high cohesion and enables clear navigation through your code. Opaque types can reduce coupling. In packages, this is crucial for encapsulation. In applications, you can tolerate more openness but should still aim for clarity of boundaries. This pattern draws directly from Parnas' principles of modular design. ### (SOLID Principles) Single Responsibility Principle (SRP) ### Elm Code Example ```elm -- File: User.elm module User exposing (User, fromName, getName) type User = User { name : String } fromName : String -> User fromName name = User { name = name } getName : User -> String getName (User record) = record.name ``` --- ## Ports are OK ### When to Use Use ports when you need to interop with JavaScript, especially for side effects Elm can’t handle natively. ### Full Description Elm developers sometimes avoid ports because of their verbosity, but they are safe and effective when used properly. You can organize port usage cleanly by putting all port definitions in a single module and handling decoding and encoding at the boundary. While task ports would be more ergonomic, standard ports are good enough for many use cases. ### (SOLID Principles) Dependency Inversion Principle (DIP) ### Elm Code Example ```elm -- PortModule.elm port module PortModule exposing (..) port alert : String -> Cmd msg ``` --- ## Nested TEA ### When to Use Use when you have N similar components on a page that each need their own state and side effects. ### Full Description Avoid overusing The Elm Architecture (TEA) in a deeply nested way. While it's tempting to wrap every component in its own `Model`, `update`, and `view`, this leads to unnecessary complexity. Instead, break out update-like or view-like functions as needed. Only use nested TEA when you truly have multiple instances of something, like a list of UI elements each with their own local state or side effects. ### (SOLID Principles) Single Responsibility Principle (SRP) ### Elm Code Example ```elm -- Component.elm module Component exposing (Model, Msg, update, view) type alias Model = Int type Msg = Increment update : Msg -> Model -> Model update msg model = case msg of Increment -> model + 1 view : Model -> Html Msg view model = Html.button [ Html.Events.onClick Increment ] [ Html.text (String.fromInt model) ] ``` --- ## Domain Modelling ### When to Use Use this for defining core logic and rules that should be side-effect free and fully testable. ### Full Description A domain model is a pure representation of your application's logic and rules. It contains types and functions that describe valid operations and transformations on your data. It should be free of `Cmd`, `Sub`, or `Html`, and fully testable with elm-test. This makes reasoning about and testing your application simpler and more robust. ### (SOLID Principles) Single Responsibility Principle (SRP), Interface Segregation Principle (ISP) ### Elm Code Example ```elm -- Price.elm module Price exposing (Price, add, fromCents) type Price = Price Int fromCents : Int -> Price fromCents cents = Price cents add : Price -> Price -> Price add (Price a) (Price b) = Price (a + b) ``` --- ## Parse don't Validate ### When to Use Use when accepting user input or converting untrusted data into domain types. ### Full Description Rather than validating input and passing raw types like `String` around, parse input into domain-specific types early. This eliminates illegal states from your core logic. Keep the raw input for error messages, but only allow parsed values through your program logic. This pattern ensures that illegal values are caught at the boundary and not deferred. ### (SOLID Principles) Open/Closed Principle (OCP), Interface Segregation Principle (ISP) ### Elm Code Example ```elm type alias RawInput = { raw : String, parsed : Maybe Int } parseInput : String -> RawInput parseInput str = { raw = str, parsed = String.toInt str } ``` --- ## State Machines ### When to Use Use this to manage UI transitions, asynchronous flows, or any logic with well-defined discrete states. ### Full Description Model your application or component state explicitly using a custom type with constructors representing distinct states. Transitions should be controlled and legal within the logic of your app. This works especially well for asynchronous operations like HTTP requests, where you might represent `Idle`, `Loading`, `Success`, and `Failure` states. State machines help prevent illegal transitions and make all possible states visible and testable. ### (SOLID Principles) Single Responsibility Principle (SRP), Open/Closed Principle (OCP) ### Elm Code Example ```elm type RemoteData data = NotAsked | Loading | Success data | Failure Http.Error update : Msg -> Model -> (Model, Cmd Msg) update msg model = case (msg, model.remoteData) of (Fetch, NotAsked) -> ({ model | remoteData = Loading }, fetchCmd) (Fetched (Ok data), Loading) -> ({ model | remoteData = Success data }, Cmd.none) (Fetched (Err err), Loading) -> ({ model | remoteData = Failure err }, Cmd.none) _ -> (model, Cmd.none) ``` --- ## Style Guide CSS ### When to Use Use this when applying traditional CSS styling via class names in your Elm view code. ### Full Description Avoid embedding raw strings like `"btn primary"` throughout your code. Instead, define a custom type or central list of classes to reference. This gives you compile-time checking of class names, and it helps organize style as a separate concern from layout or behavior. ### (SOLID Principles) Interface Segregation Principle (ISP) ### Elm Code Example ```elm type CssClass = Primary | Secondary toClassName : CssClass -> String toClassName cls = case cls of Primary -> "btn-primary" Secondary -> "btn-secondary" button : CssClass -> String -> Html msg button cls label = Html.button [ Html.Attributes.class (toClassName cls) ] [ Html.text label ] ``` --- ## Style Guide elm-ui ### When to Use Use this when you want layout to be type-safe and CSS-free, or mix with CSS for hybrid styling. ### Full Description `elm-ui` replaces HTML and CSS with a purely Elm-based layout engine. It enforces constraints at compile time and eliminates a class of layout bugs. Even with `elm-ui`, you should still separate layout and style concerns by organizing layout code into helper functions or modules that reflect visual structure. ### (SOLID Principles) Single Responsibility Principle (SRP) ### Elm Code Example ```elm import Element exposing (..) import Element.Background as Background import Element.Font as Font view : Element msg view = column [ spacing 20, padding 20 ] [ el [ Font.bold ] (text "Welcome") , el [ Background.color (rgb255 240 240 255) ] (text "Hello!") ] ``` --- ## Web Components ### When to Use Use this when you need to integrate complex custom HTML elements or third-party widgets into your Elm app. ### Full Description Web Components can encapsulate functionality that would be painful or impossible to implement in pure Elm. These include WYSIWYG editors, sliders, charts, or canvas-based visualizations. You interact with them via standard DOM attributes, properties, and events. Elm can listen for custom events via ports or `Html.Events.on`, and you can set up flags or attributes to pass data in. ### (SOLID Principles) Interface Segregation Principle (ISP), Dependency Inversion Principle (DIP) ### Elm Code Example ```elm Html.node "my-slider" [ Html.Attributes.attribute "value" "42" , Html.Events.on "change" (Decode.map SliderChanged Decode.string) ] [] ``` --- ## Defunctionalization ### When to Use Use this when you want to model function behavior as data, enabling interpretation or delayed evaluation. ### Full Description Defunctionalization is the process of replacing a function with a data structure that describes what the function would do. This is useful when you want to manipulate, serialize, inspect, or sequence operations. `Msg` in Elm is a defunctionalized representation of event handlers. Other uses include building syntax trees or encoding program logic for later evaluation. ### (SOLID Principles) Open/Closed Principle (OCP) ### Elm Code Example ```elm type Expr = Add Int Int | Multiply Int Int evaluate : Expr -> Int evaluate expr = case expr of Add a b -> a + b Multiply a b -> a * b ``` --- ## Actor Model Fetish ### When to Use Use cautiously if routing messages between many components, or building dynamic message handlers. ### Full Description The actor model can be a powerful way to model distributed, asynchronous systems. In Elm, this pattern is sometimes mimicked using message passing between components. However, overuse leads to complex Msg routing and unclear state ownership. This adds indirection and hurts performance and maintainability. Prefer flat state and function composition unless you truly benefit from modular actors with encapsulated behavior. ### (SOLID Principles) Can violate SRP and ISP if misused. A form of Defunctionalization. ### Elm Code Example ```elm -- Msg wrapping pattern (can get complex quickly) type Msg = Component1Msg Component1.Msg | Component2Msg Component2.Msg ``` --- ## IO Monad Fetish ### When to Use Use this term to describe over-abstracting side effects, especially when modeling them unnecessarily as Cmd wrappers or effect managers. ### Full Description While it’s tempting to treat Cmd like an IO Monad and encapsulate everything into abstract effect layers, this often leads to unnecessary indirection. Elm already handles side effects in a clean, declarative way. Encapsulate effects by constraining `Msg`, not by creating custom effect managers unless truly necessary. Limit exposure of Msgs used for side effects so they remain internal to the module that owns them. ### (SOLID Principles) Interface Segregation Principle (ISP), Dependency Inversion Principle (DIP) ### Elm Code Example ```elm -- Internal module (does side effects) type Msg = Save | Saved update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of Save -> (model, saveToServer model) Saved -> (model, Cmd.none) ``` --- ## Poor Man’s Typeclass ### When to Use Use this when you want to abstract behavior using a record of functions (like an interface) instead of typeclasses. ### Full Description Elm doesn’t have typeclasses, but you can achieve similar abstraction by passing records of functions. This allows pluggable behavior and simple dependency injection. The limitation is that recursive functions over such types expose internal structure via type parameters, making substitution trickier and breaking the open/closed principle in some cases. ### (SOLID Principles) Dependency Inversion Principle (DIP), Partial support for Open/Closed Principle (OCP) ### Elm Code Example ```elm type alias Logger = { log : String -> Cmd msg } printMessage : Logger -> String -> Cmd msg printMessage logger message = logger.log ("LOG: " ++ message) ``` --- ## Create Your Own Effects ### When to Use Use when Cmd or Sub creation logic needs to be abstracted or bundled as data. ### Full Description Although only core `elm/*` packages can define true kernel effects, you can simulate user-defined effects using records of functions and libraries like `elm-procedure`. This lets you create custom effect APIs, defer execution, or centralize effect definitions for testability or reuse. ### (SOLID Principles) Dependency Inversion Principle (DIP), Single Responsibility Principle (SRP) ### Elm Code Example ```elm type alias Effect msg = { perform : () -> Cmd msg } myEffect : Effect msg myEffect = { perform = \_ -> Debug.log "Running!" (Cmd.none) } ``` --- ## Object Oriented Elm ### When to Use Use this when you want to define pluggable behavior with self-contained modules and open-ended extension. ### Full Description You can simulate OO design by using records of functions (i.e., function objects). Instead of calling `function object`, you flip to `object.function`, encapsulating behavior and supporting new ‘subtypes’ without touching old code. This is how you achieve the Open/Closed Principle and even Liskov Substitution in Elm. Useful in interpreters, dynamic systems, or plugin-style architectures. ### (SOLID Principles) Open/Closed Principle (OCP), Liskov Substitution Principle (LSP) ### Elm Code Example ```elm type alias Shape = { area : Float } circle : Float -> Shape circle r = { area = pi * r * r } square : Float -> Shape square s = { area = s * s } ``` --- -
rupertlssmith revised this gist
Aug 19, 2025 . 1 changed file with 136 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,136 @@ ## Life of a File ### When to Use Use this approach at the start of any Elm project. Many projects never need to grow beyond it. ### Full Description This pattern emphasizes working with a single Elm file containing `Model`, `Msg`, `update`, and `view`. It keeps things simple, fast to navigate, and easy to understand. Use it to sketch ideas, prototype features, or build small apps. Don't prematurely modularize. Instead, let the shape of the application emerge organically. Trust your hands. Elm composes well; you can always refactor later. ### (SOLID Principles) None directly; the goal is simplicity before design principles are required. ### Elm Code Example ```elm module Main exposing (..) import Browser import Html exposing (Html, div, text) import Html.Events exposing (onClick) -- MODEL type alias Model = Int init : Model init = 0 -- UPDATE type Msg = Increment update : Msg -> Model -> Model update msg model = case msg of Increment -> model + 1 -- VIEW view : Model -> Html Msg view model = div [] [ div [] [ text ("Count: " ++ String.fromInt model) ] , Html.button [ onClick Increment ] [ text "Increment" ] ] -- MAIN main = Browser.sandbox { init = init, update = update, view = view } ``` --- ## Smallest Type ### When to Use Use this pattern when working with large records or shared data structures across modules. ### Full Description Functions should only require access to the exact fields they need. By using extensible records, you can prevent unnecessary coupling. This is especially helpful when building components or modules that work with slices of the full `Model`. Avoid 'stringly typed' fields and always prefer custom types or restricted enums over raw `String` or `Int` when the domain allows. ### (SOLID Principles) Interface Segregation Principle (ISP) ### Elm Code Example ```elm type alias FullModel = { name : String , age : Int , isAdmin : Bool } type alias Nameable r = { r | name : String } greet : Nameable r -> String greet model = "Hello, " ++ model.name ``` --- ## Higher Order Functions ### When to Use Use when you want to inject custom behavior into a reusable abstraction, e.g. with `map`, `andThen`, `foldl`. ### Full Description Functions are first-class in Elm, which enables powerful abstractions. A higher-order function takes another function as an argument or returns one. This enables dependency injection via pure functions. This pattern is foundational in functional programming and underpins many libraries and patterns in Elm. ### (SOLID Principles) Dependency Inversion Principle (DIP) ### Elm Code Example ```elm List.map (\x -> x * 2) [1, 2, 3] -- [2, 4, 6] Maybe.andThen String.toInt (Just "42") -- Just 42 -- A custom example applyTwice : (a -> a) -> a -> a applyTwice f x = f (f x) ``` --- ## Opaque Types ### When to Use Use when you want to hide implementation details of a type from consumers of your module. ### Full Description By exposing a type without its constructor, you create an abstraction barrier. This lets you control how values are created and manipulated. Useful in packages and encapsulated logic to preserve invariants. ### (SOLID Principles) Interface Segregation Principle (ISP), Single Responsibility Principle (SRP), Open/Closed Principle (OCP) ### Elm Code Example ```elm -- UserId.elm module UserId exposing (UserId, fromString, toString) type UserId = UserId String fromString : String -> Maybe UserId fromString str = if String.length str > 0 then Just (UserId str) else Nothing toString : UserId -> String toString (UserId str) = str ``` --- -
rupertlssmith revised this gist
Aug 19, 2025 . 1 changed file with 27 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,34 +2,28 @@ ## Quick Overview of Elm Elm is a pure typed functional language with fully decidable type inference. The syntax of the language is ML-style and very minimalist; in most cases there is only 1 way of writing something since there is little syntactic sugar. It has no user space escape hatches for mutation or direct execution of side effects outside of its runtime IO wrapper. The runtime IO wrapper provides commands (`Cmd a`) for requesting side effects, subscriptions (`Sub a`) for receiving notifications of events, and tasks (`Task err a`) for side effects that are guaranteed to provide some response. It is targetted at writing web applications and compiles to javascript, although it can also run outside of the browser. Elm has a powerful type system that yields a type for any expression or construct in the language. Type checking is muc more 'complete' than in most other languages and greatly reduces the potential bugs that can be written in code that compiles. The compiler provides helpful error messages that are much easier to read than more accademic functional programming languages, making it approachable and beginner friendly. The type system supports fully decidable checking and inference; you can write untyped Elm code and have the compiler infer the type automatically. Elm has sum types (custom types) and product types (records) in the ML style. It has type variables enabling parametric polymorphism. Elm also has a form of parametric product types called extensible records, which are useful for writing polymorphic functions over records where the full type of the record does not need to be revealed. Functions are typed with arrows `->` and are normally curried. Tuples up to 3 entries in size are allowed as a pragmatic limit, for larger products records with named fields are required. Custom type constructors can be pattern matched in function arguments, let variables or case statements. The Elm Architecture (TEA) is a Model-Update-View pattern for writing web applications. There is 1 update cycle per javascript event loop iteration. The view cycle usually runs once per animation frame, but there are optimisations where the view and update can run synchronously without waiting for the next animation frame in response to DOM events (described in the docs for elm/virtual-dom). The `view` is rendered from your `Model` describing the state of your application. The `Model` can be replaced with a new `Model` by your `update` code, which can also request some side effects be run. The runtime aims to produce no runtime exceptions at all; in practice there are ways to cause runtime exceptions in Elm but they are rare and generally not an issue. This also means that exceptions in arithmetic that arise naturally from mathematics are implemented in Elm instead by giving the wrong answer. For example, dividing by integer zero produces zero, not an exception. ## What is the Elm equivalent of SOLID? SOLID is a mnemonic acronym for a group of five good rules of thumb in computer programming. Often applied in the context of OO programming, it can also translate reasonably well into Elm. * Single Responsibility Principle (SRP) - A class should have only one reason to change, meaning it should have one specific job or responsibility. * Open/Closed Principle (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means you should be able to add new functionality without altering existing code. * Liskov Substitution Principle (LSP) - Subtypes must be substitutable for their base types without altering the correctness of the program. * Interface Segregation Principle (ISP) - Clients should not be forced to depend on interfaces they don't use. This principle encourages creating smaller, more specific interfaces rather than large, monolithic ones. * Dependency Inversion Principle (DIP) - Depend upon abstractions, not concretions. High-level modules should not depend on low-level modules, but both should depend on abstractions. Is SOLID the right rule set for Elm, or do we need a different set of principles for functional programming? To answer that question I have come up with a set of design patterns for Elm packages and applications and tagged them with any SOLID rules that they follow. ## Stairway to Heaven: Elm is a language without minimal syntax, but there is a high degree of orthogonaility in that syntax that allows you to combine in increasingly powerful ways. But always in Elm that power comes with a complexity cost. The effort/reward/complexity/risk scale therefore lines up quite well in Elm. Only pay for the reward or complexity or risk that you really need with your available effort. @@ -74,4 +68,22 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Create Your Own Effects - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. * Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to. (OCP, LSP) ## Writing Ideas... Just dumping some thoughts here to tidy up later. Elm compiling to javascript means it runs as single threaded event driven programming. with immutable data structures. This means that it is safe to share state over your entire application. This in turn means that building applications in Elm is best done as 'flat' as possible. The reason why? Otherwise state will be encapsulated to a module and you may get into the anti-pattern of having modules send events to other modules to update or query state. Share state between modules where needed as single threaded and immutable cancels out the difficulties this would pose in multi-threaded object oriented languages. Use extensible records to describe the sub-parts of the model that modules need to work with and keep the number of exposed fields in each slice of the record minimal for each part of your application. Name modules after the principal type they expose. When slicing a model into extensible records, give the record alias for each sub-module the same name as the module, and build the module around that type. Applications grow hair and typically end up with lots of fields in their top-level module. Things like URLs for backends they work with, config settings, user settings. And application models can get big as the complexity of an application grows. This is normal and totally fine in application code. There is a temptation to tidy this that should be resisted as you want to avoid encapsulating data into one module and falling into a message passing regime with it. Do tidy stuff into sub-models when it all neatly falls within the scope of a single area of the application and you are sure of that, just don't rush to this position. When building re-usable components, which can be published as packages even, fields typically do get encapsulated. Again, don't rush to get this, let re-usable code surface in its own time. But it is a good idea to have patterns ready in mind how to design re-usable components and their APIs. State machines are your best friends in Elm. This can give Elm a feel somewhat similar to digital circuit design languages like VHDL and Verilog. Sometimes I call my Elm entry point Top.elm instead of Main.elm for this reason. Top.elm or Main.elm exists to wire together your application. Ideally as a functional program that composes its parts together by function composition, and not as a routing bus in an actor model system. One of the reasons Actor model is a pain in Elm is that the Event type needs to be known everywhere or translated and this always causes either close coupling in the application or elaborate efforts to avoid that with translation and then a maintenance headache to manage it all. This way is also a form of defunctionalization. -
rupertlssmith revised this gist
Aug 19, 2025 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -38,19 +38,19 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. * Smallest Type - Keep your types as small as possible. This mainly applies to records in Elm. You can have a function over a record that does not use all fields in the record and it is good practice to replace this with an extensible record that only reveals the actual fields used. Avoid stringly typed things that can be described by more specific types and use custom types. (ISP) * Higher Order Functions - The most common ones you will encounter are `map` and `andThen` both of these take functions as arguments, apply them so some kind of wrapped entity, list elements, a `Cmd a` or `Html a`, or a `Decoder a` and so on. This is a form of dependency injection, where you supply a "callback function" that provides custom behaviour over some regular and re-usable construct in the language. This is a big part of what makes functional programming fun and powerful! Functions are first class entities in the language and can be passed around like any other value. (DIP) * Opaque Types - You can hide the constructors of a custom type so they are not exposed by a Module. This is a great way of exposing an API over some type but without exposing the implementation can lead to lower coupling and better separation of concerns in your design. (ISP) * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. (SRP) * Ports are OK - Elm programmers sometimes get a bit hung up on ports. Certainly doing an synchronous call via ports is a bit inconvenient and we wish we had task ports for this. But it isn't a big deal and often leads to cleanly organized code so just use ports when you need to. * Nested TEA - The Elm Architecture is Elms Model-Update-View pattern. Avoid making everything a TEA-like component with its own encapsulated model. Everyone tries this at least once. The only tim eyou really need to do this is if you have N things on a page. For example, if I have a list of N items which are custom elements of my UI and create side effects, I will need each of them to have its own encapsulated `Model` and `update` and `view`. For single use constructs work on breaking out update-like functions or view-like functions into modules instead of complete TEA-like components. See "Smallest Types". * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. @@ -74,4 +74,4 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Create Your Own Effects - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. * Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to. (OCP, LSP) -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -41,20 +41,22 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Smallest Type - Keep your types as small as possible. This mainly applies to records in Elm. You can have a function over a record that does not use all fields in the record and it is good practice to replace this with an extensible record that only reveals the actual fields used. Avoid stringly typed things that can be described by more specific types and use custom types. * Higher Order Functions - The most common ones you will encounter are `map` and `andThen` both of these take functions as arguments, apply them so some kind of wrapped entity, list elements, a `Cmd a` or `Html a`, or a `Decoder a` and so on. This is a form of dependency injection, where you supply a "callback function" that provides custom behaviour over some regular and re-usable construct in the language. This is a big part of what makes functional programming fun and powerful! Functions are first class entities in the language and can be passed around like any other value. * Opaque Types - You can hide the constructors of a custom type so they are not exposed by a Module. This is a great way of exposing an API over some type but without exposing the implementation can lead to lower coupling and better separation of concerns in your design. * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. * Ports are OK - Elm programmers sometimes get a bit hung up on ports. Certainly doing an synchronous call via ports is a bit inconvenient and we wish we had task ports for this. But it isn't a big deal and often leads to cleanly organized code so just use ports when you need to. * Nested TEA - The Elm Architecture is Elms Model-Update-View pattern. Avoid making everything a TEA-like component with its own encapsulated model. Everyone tries this at least once. The only tim eyou really need to do this is if you have N things on a page. For example, if I have a list of N items which are custom elements of my UI and create side effects, I will need each of them to have its own encapsulated `Model` and `update` and `view`. For single use constructs work on breaking out update-like functions or view-like functions into modules instead of complete TEA-like components. See "Smalled Types". * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. * Parse don't Validate - If you validate user input but do not parse it, when you later write a function over it, your function will still have to deal with illegal states. For example, user inputs a number as text. My function adds their input to some value. If the input to the add function is a String, I must still consider cases where it is not a number, even though I earlier validated it. If I parsed it into an Int at validation time instead, I can now safely add it without considering the illegal state which was entirely eliminated as the data crossed over into my domain model from the IO controller. Keep the raw String too, as you may still need to show it to the user for error reporting purposes. * State Machines - Elm and State machines go together like coffee and cigarettes. A state machine is a great way to design a UI with states corresponding to visual states the user will see and interact with, as well as some tranisitory states such as "waiting for data" that are needed to ensure the UI correctly progresses through legal state transitions only and can recover from errors such as "fetching data failed". An Elm program is single threaded and sequential. The real world is parallel. A state machine can marshall real world events into a sequential ordering. Consider the coca cola vending machine example (irn bru machine in Scotland) which needs some internal states to prevent the user pressing the "vend" and "coin return" buttons at the same time and getting a free drink. * Style Guide CSS - Just as with HTML/CSS it is usually beneficial to separate content and style. The reason? So that style can evolve independantly and so that style is a thing in itself with its own specialists. If style is buried in application view logic, it can become harder to change. A clean separation is usually achieved by making use of good old CSS classes. You can describe your CSS classes with a custom type and get the benefit of typechecking so that you never typo a class name again. @@ -70,6 +72,6 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Poor Mans Typeclass - The type signature of a record of functions can act as an "interface" that describes a type class in Elm. You can use such interfaces to describe the behaviour of objects in a way that leaves the implementation of the object abstract. The problem in Elm is that when you make such records recursive (by wrapping in a custom types or recursive over a type parameter) you will find that the type of the implementation of the object will naturally pop out as a type parameter. As a result the poor mans typeclass does is not drop-in Liskov substitutable and not quite a realisation of the Open-Closed principle; since different implementations alter the type of the surrounding program. Useful when you want higher order functions that allow many function parameters to be injected, as the use of a record lets you name them. Very useful for implementing dependency injection between modules as function composition. * Create Your Own Effects - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. * Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to. -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 6 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -35,16 +35,19 @@ SOLID is a mnemonic acronym for a group of five good rules of thumb in computer Elm is a language without minimal syntax, but there is a high degree of orthogonaility in that syntax that allows you to combine in increasingly powerful ways. But always in Elm that power comes with a complexity cost. The effort/reward/complexity/risk scale therefore lines up quite well in Elm. Only pay for the reward or complexity or risk that you really need with your available effort. So I made a list of steps of increasing effort up to more powerful techniques and arranged them in order. At the base are the simpler and more used techniques, and as you get higher, you get more complex and expensive techniques that should also be used less. * Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. * Smallest Type - Keep your types as small as possible. This mainly applies to records in Elm. You can have a function over a record that does not use all fields in the record and it is good practice to replace this with an extensible record that only reveals the actual fields used. Avoid stringly typed things that can be described by more specific types and use custom types. * Higher Order Functions - The most common ones you will encounter are `map` and `andThen` both of these take functions as arguments, apply them so some kind of wrapped entity, list elements, a `Cmd a` or `Html a`, or a `Decoder a` and so on. This is a form of dependency injection, where you supply a "callback function" that provides custom behaviour over some regular and re-usable construct in the language. This is a big part of what makes functional programming fun and powerful! Functions are first class entities in the language and can be passed around like any other value. * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. * Ports are OK - Elm programmers sometimes get a bit hung up on ports. Certainly doing an synchronous call via ports is a bit inconvenient and we wish we had task ports for this. But it isn't a big deal and often leads to cleanly organized code so just use ports when you need to. * Nested TEA - The Elm Architecture is Elms Model-Update-View pattern. * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. @@ -57,6 +60,8 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Style Guide elm-ui - Even without the common CSS-as-style approach, elm-ui is a good choice for Elm also. It is fast and powerful. Still make a style guide out of your styling code and maintain a stlye/content split. You can mix both elm-ui and CSS too, use elm-ui more for layout purposes and stick with CSS for the style. * Web Components - In addition to ports you can also interact with web compoments. If you need something custom in your DOM and Elm is a pain to do it with, consider a web component and how you will use its attributes, properties and events to communicate with your Elm code. Some powerful things can be achieved this way, such as a WYSIWYG text editor in content editable mostly written in Elm. * Defunctionalization - This is when functions return data types describing the partially evaluated output of the function, instead of having a total function that outputs its fully evaluated result. For example, in a calulator application, instead of evaluating down to a number as the user enters each operation, you could gather the operations into a syntax tree, with ops as nodes and numbers as leafs. The advantage is flexibility - you could display the syntax tree, or you could optimize the syntax tree before evaluation and so on. Note that `Msg` in Elm is a form of defunctionalization. The "out message" pattern in Elm is also a form of defuctionalization and on that makes update-like functions using it, less total. An update with an out message is saying that the update function could not complete all its work and needs to return a partial evaluation for some other function to complete - but what is the advantage obtained for the extra cost here? Use defunctionalization when you have a clear use case for doing so, as with all the techniques described here. * Actor Model Fetish - You naughty Elixir or Erlang programmer! But seriously the actor model is a great pattern, and Erlang concurrency is amongst the safest in no small part due to following that model. It is also inevitable that you will encounter this pattern in Elm. It is fine to use it so long as you are aware of the pitfalls. The danger is that the routing of events between modules becomes hard to understand and grows into a maintenance cost for an application. If this happens, then simple things like accessing or updating fields in your application model become a hassle and kills development speed. Also when you communicate with Msgs as events in a state machine, the more Msgs you have, the more complex your state machine needs to be. This creates more surface area for undesirable state to accumulate and can make application logic more complex than it really needs to be. Note that actor model with "out messages" is a form of Defunctionalization. Your update functions become partial functions! -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 7 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,21 +2,21 @@ ## Quick Overview of Elm TEA (The Elm Architecture) is Module-Update-View. There is 1 update cycle per javascript event loop iteration. There are some situations where update cycles can be triggered synchronously described in the docs for elm/virtual-dom; its some browser optimisation stuff. Usually the view cycle is triggered once per animation frame, and the update cycle can run at a faster tempo if events come in faster than that. This is single threaded event driven programming with immutable data structures. This means that it is safe to share state over your entire application. This in turn means that building applications in Elm is best done as 'flat' as possible. The reason why? Otherwise state will be encapsulated to a module and you may get into the anti-pattern of having modules send events to other modules to update or query state. Share state between modules where needed as single threaded and immutable cancels out the difficulties this would pose in multi-threaded object oriented languages. Use extensible records to describe the sub-parts of the model that modules need to work with and keep the number of exposed fields in each slice of the record minimal for each part of your application. Name modules after the principal type they expose. When slicing a model into extensible records, give the record alias for each sub-module the same name as the module, and build the module around that type. Applications grow hair and typically end up with lots of fields in their top-level module. Things like URLs for backends they work with, config settings, user settings. And application models can get big as the complexity of an application grows. This is normal and totally fine in application code. There is a temptation to tidy this that should be resisted as you want to avoid encapsulating data into one module and falling into a message passing regime with it. Do tidy stuff into sub-models when it all neatly falls within the scope of a single area of the application and you are sure of that, just don't rush to this position. When building re-usable components, which can be published as packages even, fields typically do get encapsulated. Again, don't rush to get this, let re-usable code surface in its own time. But it is a good idea to have patterns ready in mind how to design re-usable components and their APIs. State machines are your best friends in Elm. This can give Elm a feel somewhat similar to digital circuit design languages like VHDL and Verilog. Sometimes I call my Elm entry point Top.elm instead of Main.elm for this reason. Top.elm or Main.elm exists to wire together your application. Ideally as a functional program that composes its parts together by function composition, and not as a routing bus in an actor model system. One of the reasons Actor model is a pain in Elm is that the Event type needs to be known everywhere or translated and this always causes either close coupling in the application or elaborate efforts to avoid that with translation and then a maintenance headache to manage it all. This way is also a form of defunctionalization. -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 4 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -38,10 +38,14 @@ So I made a list of steps of increasing effort up to more powerful techniques an * Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. * Higher Order Functions - The most common ones you will encounter are `map` and `andThen` both of these take functions as arguments, apply them so some kind of wrapped entity, list elements, a `Cmd a` or `Html a`, or a `Decoder a` and so on. This is a form of dependency injection, where you supply a "callback function" that provides custom behaviour over some regular and re-usable construct in the language. This is a big part of what makes functional programming fun and powerful! Functions are first class entities in the language and can be passed around like any other value. * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. * Nested TEA - The Elm Architecture is Elms Model-Update-View pattern. * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 15 additions and 26 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -37,41 +37,30 @@ Elm is a language without minimal syntax, but there is a high degree of orthogon So I made a list of steps of increasing effort up to more powerful techniques and arranged them in order. At the base are the simpler and more used techniques, and as you get higher, you get more complex and expensive techniques that should also be used less. * Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. * Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. * Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. * Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. * Parse don't Validate - If you validate user input but do not parse it, when you later write a function over it, your function will still have to deal with illegal states. For example, user inputs a number as text. My function adds their input to some value. If the input to the add function is a String, I must still consider cases where it is not a number, even though I earlier validated it. If I parsed it into an Int at validation time instead, I can now safely add it without considering the illegal state which was entirely eliminated as the data crossed over into my domain model from the IO controller. Keep the raw String too, as you may still need to show it to the user for error reporting purposes. * State Machine - Elm and State machines go together like coffee and cigarettes. A state machine is a great way to design a UI with states corresponding to visual states the user will see and interact with, as well as some tranisitory states such as "waiting for data" that are needed to ensure the UI correctly progresses through legal state transitions only and can recover from errors such as "fetching data failed". An Elm program is single threaded and sequential. The real world is parallel. A state machine can marshall real world events into a sequential ordering. Consider the coca cola vending machine example (irn bru machine in Scotland). * Style Guide CSS - Just as with HTML/CSS it is usually beneficial to separate content and style. The reason? So that style can evolve independantly and so that style is a thing in itself with its own specialists. If style is buried in application view logic, it can become harder to change. A clean separation is usually achieved by making use of good old CSS classes. You can describe your CSS classes with a custom type and get the benefit of typechecking so that you never typo a class name again. * Style Guide elm-ui - Even without the common CSS-as-style approach, elm-ui is a good choice for Elm also. It is fast and powerful. Still make a style guide out of your styling code and maintain a stlye/content split. You can mix both elm-ui and CSS too, use elm-ui more for layout purposes and stick with CSS for the style. * Defunctionalization - This is when functions return data types describing the partially evaluated output of the function, instead of having a total function that outputs its fully evaluated result. For example, in a calulator application, instead of evaluating down to a number as the user enters each operation, you could gather the operations into a syntax tree, with ops as nodes and numbers as leafs. The advantage is flexibility - you could display the syntax tree, or you could optimize the syntax tree before evaluation and so on. Note that `Msg` in Elm is a form of defunctionalization. The "out message" pattern in Elm is also a form of defuctionalization and on that makes update-like functions using it, less total. An update with an out message is saying that the update function could not complete all its work and needs to return a partial evaluation for some other function to complete - but what is the advantage obtained for the extra cost here? Use defunctionalization when you have a clear use case for doing so, as with all the techniques described here. * Actor Model Fetish - You naughty Elixir or Erlang programmer! But seriously the actor model is a great pattern, and Erlang concurrency is amongst the safest in no small part due to following that model. It is also inevitable that you will encounter this pattern in Elm. It is fine to use it so long as you are aware of the pitfalls. The danger is that the routing of events between modules becomes hard to understand and grows into a maintenance cost for an application. If this happens, then simple things like accessing or updating fields in your application model become a hassle and kills development speed. Also when you communicate with Msgs as events in a state machine, the more Msgs you have, the more complex your state machine needs to be. This creates more surface area for undesirable state to accumulate and can make application logic more complex than it really needs to be. Note that actor model with "out messages" is a form of Defunctionalization. Your update functions become partial functions! * IO Monad Fetish - The true fetish of functional programmers. We can push all that unpredictable side effecty stuff to one side. Side effects in Elm as Cmd or your own Effect type are forms of Defunctionalization. You can keep this to miniumum by only having Msgs for side effects, and keeping Msg opaque outside of a component of the application - it exists purely for the processing of side effects that are entirely internal to the module. Instead of encapsulating data fields like in object oriented programming, we want to encapsulate side effects when extracting code as a component in Elm. * Poor Mans Typeclass - The type signature of a record of functions can act as an "interface" that describes a type class in Elm. You can use such interfaces to describe the behaviour of objects in a way that leaves the implementation of the object abstract. The problem in Elm is that when you make such records recursive (by wrapping in a custom types or recursive over a type parameter) you will find that the type of the implementation of the object will naturally pop out as a type parameter. As a result the poor mans typeclass does is not drop-in Liskov substitutable and not quite a realisation of the Open-Closed principle; since different implementations alter the type of the surrounding program. Useful when you want higher order functions that allow many function parameters to be injected, as the use of a record lets you name them. Very useful for implementing dependency injection between modules as function composition. * Create Your Own Effects modules - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. * Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to. -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 10 additions and 15 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -25,26 +25,21 @@ One of the reasons Actor model is a pain in Elm is that the Event type needs to SOLID is a mnemonic acronym for a group of five good rules of thumb in computer programming. Often applied in the context of OO programming, it can also translate well into Elm. * Single Responsibility Principle (SRP) - A class should have only one reason to change, meaning it should have one specific job or responsibility. * Open/Closed Principle (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means you should be able to add new functionality without altering existing code. * Liskov Substitution Principle (LSP) - Subtypes must be substitutable for their base types without altering the correctness of the program. * Interface Segregation Principle (ISP) - Clients should not be forced to depend on interfaces they don't use. This principle encourages creating smaller, more specific interfaces rather than large, monolithic ones. * Dependency Inversion Principle (DIP) - Depend upon abstractions, not concretions. High-level modules should not depend on low-level modules, but both should depend on abstractions. ## Stairway to Heaven: Elm is a language without minimal syntax, but there is a high degree of orthogonaility in that syntax that allows you to combine in increasingly powerful ways. But always in Elm that power comes with a complexity cost. The effort/reward/complexity/risk scale therefore lines up quite well in Elm. Only pay for the reward or complexity or risk that you really need with your available effort. So I made a list of steps of increasing effort up to more powerful techniques and arranged them in order. At the base are the simpler and more used techniques, and as you get higher, you get more complex and expensive techniques that should also be used less. 1. Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. 2. Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. -
rupertlssmith revised this gist
Aug 18, 2025 . 1 changed file with 15 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,29 +1,30 @@ # My Elm Design Philosophy ## Quick Overview of Elm TEA is Module Update View. The is 1 update cycle per javascript event loop iteration. There are some situations where update cycles can be triggered synchronously described in the docs for elm/virtual-dom; its some browser optimisation stuff. This is single threaded event driven programming and Elm is immutable. This means that it is safe to share state over your entire application. This in turn means that building applications in Elm is best done as 'flat' as possible. The reason why? Otherwise state will be encapsulated to a module and you will get into the anti pattern of having modules send events to other modules to update or query state. Share state between modules where needed as single threaded and immutable cancels out the difficulties this would pose in multi-threaded object oriented languages. Use extensible records to describe the sub-parts of the model that modules need to work with and keep this view clean for each part of your application. Name modules after the principal type they expose. When slicing a model into extensible records, give the record alias for each sub-module the same name as the module, and build the module around that type. Application grow hair and typically end up with lots of fields in their top-level module. Things like URLs for backends they work with, config settings, user settings. And application models can get big as the complexity of an application grows. This is normal and totally fine in application code. There is a temptation to tidy this that should be resisted as you want to avoid encapsulating data into one module and falling into a message passing regime with it. When building re-usable components, which can be published even as packages, fields typically do get encapsulated. Don't rush to get this, let re-usable code surface in its own time. But it is a good idea to have patterns ready in mind how to design re-usable components and their APIs. State machines are your best friends in Elm. This makes Elm somewhat similar to digital circuit design languages like VHDL and Verilog. Sometimes I call my Elm entry point Top.elm instead of Main.elm for this reason. Top.elm or Main.elm exists to wire together your application. Ideally as a functional program that composes its parts together by function composition, and not as a routing bus in an actor model. One of the reasons Actor model is a pain in Elm is that the Event type needs to be known everywhere or translated and this always causes either close coupling in the application or elaborate efforts to avoid that with translation and then a maintenance headache to manage it all. This way is also a form of defunctionalization. ## What is the Elm equivalent of SOLID? SOLID is a mnemonic acronym for a group of five good rules of thumb in computer programming. Often applied in the context of OO programming, it can also translate well into Elm. * Single Responsibility Principle (SRP) - A class should have only one reason to change, meaning it should have one specific job or responsibility. Open/Closed Principle (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means you should be able to add new functionality without altering existing code. Liskov Substitution Principle (LSP) -
rupertlssmith created this gist
Aug 18, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,81 @@ My Elm Philosophy: TEA is Module Update View. The is 1 update cycle per javascript event loop iteration. There are some situations where update cycles can be triggered synchronously described in the docs for elm/virtual-dom; its some browser optimisation stuff. This is single threaded event driven programming and Elm is immutable. This means that it is safe to share state over your entire application. This in turn means that building applications in Elm is best done as 'flat' as possible. The reason why? Otherwise state will be encapsulated to a module and you will get into the anti pattern of having modules send events to other modules to update or query state. Share state between modules where needed as single threaded and immutable cancels out the difficulties this would pose in multi-threaded object oriented languages. Use extensible records to describe the sub-parts of the model that modules need to work with and keep this view clean for each part of your application. Name modules after the principal type they expose. When slicing a model into extensible records, give the record alias for each sub-module the same name as the module, and build the module around that type. Application grow hair and typically end up with lots of fields in their top-level module. Things like URLs for backends they work with, config settings, user settings. And application models can get big as the complexity of an application grows. This is normal and totally fine in application code. There is a temptation to tidy this that should be resisted as you want to avoid encapsulating data into one module and falling into a message passing regime with it. When building re-usable components, which can be published even as packages, fields typically do get encapsulated. Don't rush to get this, let re-usable code surface in its own time. But it is a good idea to have patterns ready in mind how to design re-usable components and their APIs. State machines are your best friends in Elm. This makes Elm somewhat similar to digital circuit design languages like VHDL and Verilog. Sometimes I call my Elm entry point Top.elm instead of Main.elm for this reason. Top.elm or Main.elm exists to wire together your application. Ideally as a functional program that composes its parts together by function composition, and not as a routing bus in an actor model. One of the reasons Actor model is a pain in Elm is that the Event type needs to be known everywhere or translated and this always causes either close coupling in the application or elaborate efforts to avoid that with translation and then a maintenance headache to manage it all. This way is also a form of defunctionalization. SOLID: SOLID is a mnemonic acronym for a group of five good rules of thumb in computer programming. Often applied in the context of OO programming, it can also translate well into Elm. Single Responsibility Principle (SRP) - A class should have only one reason to change, meaning it should have one specific job or responsibility. Open/Closed Principle (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means you should be able to add new functionality without altering existing code. Liskov Substitution Principle (LSP) - Subtypes must be substitutable for their base types without altering the correctness of the program. Interface Segregation Principle (ISP) - Clients should not be forced to depend on interfaces they don't use. This principle encourages creating smaller, more specific interfaces rather than large, monolithic ones. Dependency Inversion Principle (DIP) - Depend upon abstractions, not concretions. High-level modules should not depend on low-level modules, but both should depend on abstractions. The Elm Stairway to Heaven: Elm is a language without minimal syntax, but there is a high degree of orthogonaility in that syntax that allows you to combine in increasingly powerful ways. But always in Elm that power comes with a complexity cost. The effort/reward/complexity/risk scale therefore lines up quite well in Elm. Only pay for the reward or complexity or risk that you really need with your available effort. So I made a chart of increasing effort up to more powerful techniques and arranged them in order. At the base are the simpler and more used techniques, and as you get higher, you get more complex and expensive techniques that should also be used less. Life of a File - Most Elm applications should start out this way and many will never grow beyond. Don't rush this phase. You are a potter at a wheel and you need to relax and let the pot take shape in your hands. Know how to Name Modules - Modules! Things are getting serious eh? The model names Model.elm and Msg.elm will likely always be present in Elm applications, because it is often necessary to split them out to avoid circular dependencies - but start learning how to name update-like and view-like things in Elm. The name `update` in Elm is NOT a keyword, you should name update-like things with names that reflect what they do in terms of modifying the Model or generating side-effects. View-like things should be named after visual parts of the application in ways that hopefully guide people visually to link up code and visuals. Therefore you should not really call any modules Update.elm and View.elm, but start using names that describe your particular application. Modules, Modules, Modules - Elm modules are an implementation of Parnas' principles of modular design. By following these principles you will understand better the purpose of modules and how to use them well. The aim is high cohesion and loose coupling. Modules should be built around types, and a module should almost always have the same name as its principal type. By building around a principal type, high cohesion results. Coupling can be lowered by using opaque types to hide implementation details, a technique that is particularly useful for re-usable code such as packages. Applications can usually tolerate a higher degree of coupling. Domain Modelling - A domain model in Elm is code that is side effect free. A domain model is a data model and pure code that describes the rules of the system for that data model. A domain model should be entirely testable in pure code tests run with elm-test. Scot Wlaschins book on Domain Modelling in F# translates into Elm very well and is the go to resource for learning how to do domain modelling in functional languages. Parse don't Validate - If you validate user input but do not parse it, when you later write a function over it, your function will still have to deal with illegal states. For example, user inputs a number as text. My function adds their input to some value. If the input to the add function is a String, I must still consider cases where it is not a number, even though I earlier validated it. If I parsed it into an Int at validation time instead, I can now safely add it without considering the illegal state which was entirely eliminated as the data crossed over into my domain model from the IO controller. Keep the raw String too, as you may still need to show it to the user for error reporting purposes. State Machine - Elm and State machines go together like coffee and cigarettes. A state machine is a great way to design a UI with states corresponding to visual states the user will see and interact with, as well as some tranisitory states such as "waiting for data" that are needed to ensure the UI correctly progresses through legal state transitions only and can recover from errors such as "fetching data failed". An Elm program is single threaded and sequential. The real world is parallel. A state machine can marshall real world events into a sequential ordering. Consider the coca cola vending machine example (irn bru machine in Scotland). Style Guide CSS - Just as with HTML/CSS it is usually beneficial to separate content and style. The reason? So that style can evolve independantly and so that style is a thing in itself with its own specialists. If style is buried in application view logic, it can become harder to change. A clean separation is usually achieved by making use of good old CSS classes. You can describe your CSS classes with a custom type and get the benefit of typechecking so that you never typo a class name again. Style Guide elm-ui - Even without the common CSS-as-style approach, elm-ui is a good choice for Elm also. It is fast and powerful. Still make a style guide out of your styling code and maintain a stlye/content split. You can mix both elm-ui and CSS too, use elm-ui more for layout purposes and stick with CSS for the style. Defunctionalization - This is when functions return data types describing the partially evaluated output of the function, instead of having a total function that outputs its fully evaluated result. For example, in a calulator application, instead of evaluating down to a number as the user enters each operation, you could gather the operations into a syntax tree, with ops as nodes and numbers as leafs. The advantage is flexibility - you could display the syntax tree, or you could optimize the syntax tree before evaluation and so on. Note that `Msg` in Elm is a form of defunctionalization. The "out message" pattern in Elm is also a form of defuctionalization and on that makes update-like functions using it, less total. An update with an out message is saying that the update function could not complete all its work and needs to return a partial evaluation for some other function to complete - but what is the advantage obtained for the extra cost here? Use defunctionalization when you have a clear use case for doing so, as with all the techniques described here. Actor Model Fetish - You naughty Elixir or Erlang programmer! But seriously the actor model is a great pattern, and Erlang concurrency is amongst the safest in no small part due to following that model. It is also inevitable that you will encounter this pattern in Elm. It is fine to use it so long as you are aware of the pitfalls. The danger is that the routing of events between modules becomes hard to understand and grows into a maintenance cost for an application. If this happens, then simple things like accessing or updating fields in your application model become a hassle and kills development speed. Also when you communicate with Msgs as events in a state machine, the more Msgs you have, the more complex your state machine needs to be. This creates more surface area for undesirable state to accumulate and can make application logic more complex than it really needs to be. Note that actor model with "out messages" is a form of Defunctionalization. Your update functions become partial functions! IO Monad Fetish - The true fetish of functional programmers. We can push all that unpredictable side effecty stuff to one side. Side effects in Elm as Cmd or your own Effect type are forms of Defunctionalization. You can keep this to miniumum by only having Msgs for side effects, and keeping Msg opaque outside of a component of the application - it exists purely for the processing of side effects that are entirely internal to the module. Instead of encapsulating data fields like in object oriented programming, we want to encapsulate side effects when extracting code as a component in Elm. Poor Mans Typeclass - The type signature of a record of functions can act as an "interface" that describes a type class in Elm. You can use such interfaces to describe the behaviour of objects in a way that leaves the implementation of the object abstract. The problem in Elm is that when you make such records recursive (by wrapping in a custom types or recursive over a type parameter) you will find that the type of the implementation of the object will naturally pop out as a type parameter. As a result the poor mans typeclass does is not drop-in Liskov substitutable and not quite a realisation of the Open-Closed principle; since different implementations alter the type of the surrounding program. Useful when you want higher order functions that allow many function parameters to be injected, as the use of a record lets you name them. Very useful for implementing dependency injection between modules as function composition. Create Your Own Effects modules - The elm compiler only allows this for the special elm/* kernel modules. But it can be done nicely anyway without this by using elm-procedure. The only difference is that you will end up with the functions that create the Cmd or Sub in a record instead of top-level in a module. And there will be a small amount of boilerplate wiring needed to use elm-procedure. Object Oriented Elm - In functional programming we have `function object` and in object oriented we switch that around to `object.function`. The objects themselves become self contained, and this in turn permits a system whereby new kinds of objects can be added without changing any other code. This is the open-closed principle and it also yields Likov substution both of which are what makes object orient programming attractive. You can build open ended systems this way. Most of the time you don't need to.