Back to catalog

elm-development

Comprehensive Elm functional programming for web development including The Elm Architecture, type system, JavaScript interop, and production-ready patterns

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: elm-development
description: Comprehensive Elm functional programming for web development including The Elm Architecture, type system, JavaScript interop, and production-ready patterns
---

# Elm Development

A comprehensive skill for building reliable web applications using Elm, a functional programming language that compiles to JavaScript. Elm offers no runtime errors in practice, friendly compiler messages, and a delightful development experience through strong typing and pure functional programming.

## When to Use This Skill

Use this skill when:

- Building web applications that require high reliability and zero runtime errors
- Creating SPAs (Single Page Applications) with predictable state management
- Developing user interfaces with strong type safety guarantees
- Working on projects where refactoring safety is critical
- Building applications with complex state that needs to be managed cleanly
- Creating web apps that benefit from functional programming patterns
- Developing projects where friendly error messages aid rapid development
- Integrating Elm components into existing JavaScript applications
- Building interactive web applications with real-time features
- Creating maintainable, long-lived codebases with clear contracts

## Core Concepts

### The Elm Architecture (TEA)

The Elm Architecture is a pattern for building interactive applications that emerged naturally from Elm development. It inspired Redux and similar state management patterns.

**Core Components:**

1. **Model**: The complete state of your application
2. **View**: A pure function that renders the model as HTML
3. **Update**: A pure function that updates the model based on messages

**Data Flow:**
```
Model → View → HTML
   ↑             ↓
   └── Update ←──┘
        Messages
```

**Basic Pattern:**
```elm
type alias Model =
    { count : Int }

type Msg
    = Increment
    | Decrement

init : Model
init =
    { count = 0 }

update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , div [] [ text (String.fromInt model.count) ]
        , button [ onClick Increment ] [ text "+" ]
        ]
```

### Extended Architecture with Effects

For real applications, you need side effects (HTTP, random, etc.):

**Enhanced Components:**
- **init**: Returns `(Model, Cmd Msg)` - initial state plus commands to run
- **update**: Returns `(Model, Cmd Msg)` - new state plus commands to run
- **subscriptions**: Listens to external events (time, websockets, etc.)
- **view**: Same pure function rendering HTML

**Program Types:**
- `Browser.sandbox`: Simple apps without side effects
- `Browser.element`: Apps with side effects (HTTP, etc.)
- `Browser.document`: Control over `<title>` and `<body>`
- `Browser.application`: Full SPA with URL routing

### Type System Fundamentals

Elm's type system prevents runtime errors through static analysis and type inference.

**Type Annotations:**
```elm
-- Function type annotations
add : Int -> Int -> Int
add x y = x + y

-- Record type alias
type alias User =
    { id : Int
    , name : String
    , email : String
    }

-- Custom types (sum types / tagged unions)
type UserStatus
    = Active
    | Suspended
    | Deleted

-- Parametric types
type Result error value
    = Ok value
    | Err error
```

**Type Inference:**
Elm infers types automatically, but annotations are recommended for top-level functions:

```elm
-- Compiler infers: String -> String
greet name =
    "Hello, " ++ name

-- Better: Add explicit annotation
greet : String -> String
greet name =
    "Hello, " ++ name
```

**Benefits:**
- Catches type errors at compile time
- Provides excellent error messages
- Enables fearless refactoring
- Documents function signatures
- No null/undefined errors

### Maybe and Result Types

Elm eliminates null/undefined errors using Maybe and Result.

**Maybe Type:**
```elm
type Maybe a
    = Just a
    | Nothing

-- Example: Finding a user
findUser : Int -> Maybe User
findUser id =
    -- Returns Just user if found, Nothing otherwise
    ...

-- Pattern matching on Maybe
case findUser 123 of
    Just user ->
        "Found: " ++ user.name

    Nothing ->
        "User not found"

-- Helper functions
Maybe.withDefault : a -> Maybe a -> a
Maybe.map : (a -> b) -> Maybe a -> Maybe b
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
```

**Result Type:**
```elm
type Result error value
    = Ok value
    | Err error

-- Example: Validating user input
validateAge : String -> Result String Int
validateAge input =
    case String.toInt input of
        Nothing ->
            Err "Not a valid number"

        Just age ->
            if age < 0 then
                Err "Age cannot be negative"
            else if age > 150 then
                Err "Age seems unrealistic"
            else
                Ok age

-- Using Result
case validateAge "25" of
    Ok age ->
        "Valid age: " ++ String.fromInt age

    Err error ->
        "Error: " ++ error
```

### Pattern Matching

Pattern matching is fundamental to Elm programming:

```elm
-- Matching on custom types
type Traffic
    = Red
    | Yellow
    | Green

describe : Traffic -> String
describe light =
    case light of
        Red ->
            "Stop"
        Yellow ->
            "Prepare to stop"
        Green ->
            "Go"

-- Matching on lists
describeList : List a -> String
describeList list =
    case list of
        [] ->
            "Empty list"

        [single] ->
            "One item"

        [first, second] ->
            "Two items"

        _ ->
            "Many items"

-- Matching with extraction
type User
    = Anonymous
    | LoggedIn String Int

getUserName : User -> String
getUserName user =
    case user of
        Anonymous ->
            "Guest"

        LoggedIn name _ ->
            name
```

## Language Features

### Functions

**Function Definition:**
```elm
-- Simple function
double : Int -> Int
double x =
    x * 2

-- Multiple parameters
add : Int -> Int -> Int
add x y =
    x + y

-- Partial application
add5 : Int -> Int
add5 =
    add 5

-- Anonymous functions (lambdas)
doubleList : List Int -> List Int
doubleList numbers =
    List.map (\n -> n * 2) numbers

-- Pipe operator
result : Int
result =
    [1, 2, 3, 4, 5]
        |> List.map double
        |> List.filter (\n -> n > 5)
        |> List.sum

-- Function composition
addThenDouble : Int -> Int
addThenDouble =
    add 3 >> double
```

**Let Expressions:**
```elm
calculateArea : Float -> Float -> Float
calculateArea width height =
    let
        perimeter =
            2 * (width + height)

        area =
            width * height
    in
    area
```

### Records

**Record Syntax:**
```elm
-- Record type alias
type alias Point =
    { x : Float
    , y : Float
    }

-- Creating records
origin : Point
origin =
    { x = 0, y = 0 }

-- Accessing fields
getX : Point -> Float
getX point =
    point.x

-- Field access function
.x origin  -- Returns 0

-- Updating records (immutable)
moveRight : Point -> Point
moveRight point =
    { point | x = point.x + 1 }

-- Updating multiple fields
moveDiagonal : Point -> Point
moveDiagonal point =
    { point
        | x = point.x + 1
        , y = point.y + 1
    }

-- Pattern matching on records
distance : Point -> Float
distance { x, y } =
    sqrt (x * x + y * y)
```

### Lists and Arrays

**Lists (Linked Lists):**
```elm
-- List literals
numbers : List Int
numbers =
    [1, 2, 3, 4, 5]

-- Cons operator
moreNumbers : List Int
moreNumbers =
    0 :: numbers  -- [0, 1, 2, 3, 4, 5]

-- List functions
List.map : (a -> b) -> List a -> List b
List.filter : (a -> Bool) -> List a -> List a
List.foldl : (a -> b -> b) -> b -> List a -> b
List.length : List a -> Int
List.reverse : List a -> List a
List.sort : List comparable -> List comparable
List.concat : List (List a) -> List a

-- Example usage
processNumbers : List Int -> Int
processNumbers nums =
    nums
        |> List.filter (\n -> n > 0)
        |> List.map (\n -> n * 2)
        |> List.sum
```

**Arrays (Random Access):**
```elm
import Array exposing (Array)

-- Arrays for fast random access
numbers : Array Int
numbers =
    Array.fromList [1, 2, 3, 4, 5]

-- Array operations
Array.get : Int -> Array a -> Maybe a
Array.set : Int -> a -> Array a -> Array a
Array.push : a -> Array a -> Array a
Array.length : Array a -> Int
```

### Modules and Imports

**Module Definition:**
```elm
module Utils exposing (capitalize, truncate)

-- Private function (not exposed)
isBlank : String -> Bool
isBlank str =
    String.trim str == ""

-- Public function
capitalize : String -> String
capitalize str =
    if isBlank str then
        str
    else
        String.toUpper (String.left 1 str) ++ String.dropLeft 1 str

-- Public function
truncate : Int -> String -> String
truncate maxLength str =
    if String.length str > maxLength then
        String.left (maxLength - 3) str ++ "..."
    else
        str
```

**Import Syntax:**
```elm
-- Import entire module
import List
import Dict

-- Import with alias
import Json.Decode as Decode
import Json.Encode as Encode

-- Import specific functions
import List exposing (map, filter, foldl)

-- Import all exposed functions (use sparingly)
import Html exposing (..)
```

## The Elm Architecture in Depth

### Commands (Cmd)

Commands represent side effects to perform:

```elm
type Msg
    = FetchUser
    | GotUser (Result Http.Error User)

init : (Model, Cmd Msg)
init =
    ( { user = Nothing }
    , Cmd.none  -- No command initially
    )

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FetchUser ->
            ( model
            , Http.get
                { url = "/api/user"
                , expect = Http.expectJson GotUser userDecoder
                }
            )

        GotUser result ->
            case result of
                Ok user ->
                    ( { model | user = Just user }
                    , Cmd.none
                    )

                Err _ ->
                    ( model
                    , Cmd.none
                    )

-- Combining commands
Cmd.batch : List (Cmd msg) -> Cmd msg
Cmd.batch
    [ fetchUser
    , fetchPosts
    , logEvent
    ]
```

### Subscriptions (Sub)

Subscriptions listen to external events:

```elm
import Browser.Events
import Time

type Msg
    = Tick Time.Posix
    | KeyPressed String
    | MouseMoved Int Int

subscriptions : Model -> Sub Msg
subscriptions model =
    if model.isActive then
        Sub.batch
            [ Time.every 1000 Tick
            , Browser.Events.onKeyPress keyDecoder
            , Browser.Events.onMouseMove mouseDecoder
            ]
    else
        Sub.none

-- Subscription functions
Time.every : Float -> (Time.Posix -> msg) -> Sub msg
Browser.Events.onKeyPress : Decode.Decoder msg -> Sub msg
Browser.Events.onAnimationFrame : (Time.Posix -> msg) -> Sub msg
```

### View Functions

**HTML Generation:**
```elm
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

view : Model -> Html Msg
view model =
    div [ class "container" ]
        [ header [ class "header" ]
            [ h1 [] [ text "My App" ]
            ]
        , main_ [ class "content" ]
            [ viewUserForm model.form
            , viewUserList model.users
            ]
        , footer [ class "footer" ]
            [ text "© 2025" ]
            ]

-- Conditional rendering
viewIf : Bool -> Html msg -> Html msg
viewIf condition content =
    if condition then
        content
    else
        text ""

-- List rendering
viewUserList : List User -> Html Msg
viewUserList users =
    div [ class "user-list" ]
        [ h2 [] [ text "Users" ]
        , ul [] (List.map viewUser users)
        ]

viewUser : User -> Html Msg
viewUser user =
    li [ class "user-item" ]
        [ text user.name
        , button [ onClick (DeleteUser user.id) ]
            [ text "Delete" ]
        ]
```

### State Management Patterns

**Nested Updates:**
```elm
type alias Model =
    { loginForm : LoginForm
    , settings : Settings
    }

type Msg
    = LoginFormMsg LoginFormMsg
    | SettingsMsg SettingsMsg

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        LoginFormMsg formMsg ->
            let
                (newForm, formCmd) =
                    LoginForm.update formMsg model.loginForm
            in
            ( { model | loginForm = newForm }
            , Cmd.map LoginFormMsg formCmd
            )

        SettingsMsg settingsMsg ->
            let
                (newSettings, settingsCmd) =
                    Settings.update settingsMsg model.settings
            in
            ( { model | settings = newSettings }
            , Cmd.map SettingsMsg settingsCmd
            )
```

## HTTP and JSON

### Making HTTP Requests

**GET Requests:**
```elm
import Http
import Json.Decode as Decode

type Msg
    = FetchData
    | GotData (Result Http.Error String)

fetchData : Cmd Msg
fetchData =
    Http.get
        { url = "https://api.example.com/data"
        , expect = Http.expectString GotData
        }

-- With JSON decoder
type alias User =
    { id : Int
    , name : String
    }

fetchUser : Int -> Cmd Msg
fetchUser userId =
    Http.get
        { url = "https://api.example.com/users/" ++ String.fromInt userId
        , expect = Http.expectJson GotUser userDecoder
        }

userDecoder : Decode.Decoder User
userDecoder =
    Decode.map2 User
        (Decode.field "id" Decode.int)
        (Decode.field "name" Decode.string)
```

**POST Requests:**
```elm
import Json.Encode as Encode

type Msg
    = CreateUser
    | UserCreated (Result Http.Error User)

createUser : String -> String -> Cmd Msg
createUser name email =
    Http.post
        { url = "https://api.example.com/users"
        , body = Http.jsonBody (encodeUser name email)
        , expect = Http.expectJson UserCreated userDecoder
        }

encodeUser : String -> String -> Encode.Value
encodeUser name email =
    Encode.object
        [ ("name", Encode.string name)
        , ("email", Encode.string email)
        ]
```

**Custom Requests:**
```elm
updateUser : Int -> User -> Cmd Msg
updateUser userId user =
    Http.request
        { method = "PUT"
        , headers = [ Http.header "Authorization" "Bearer token123" ]
        , url = "https://api.example.com/users/" ++ String.fromInt userId
        , body = Http.jsonBody (encodeUserUpdate user)
        , expect = Http.expectJson UserUpdated userDecoder
        , timeout = Just 10000
        , tracker = Nothing
        }
```

### JSON Decoding

**Basic Decoders:**
```elm
import Json.Decode as Decode exposing (Decoder)

-- Primitive decoders
stringDecoder : Decoder String
stringDecoder = Decode.string

intDecoder : Decoder Int
intDecoder = Decode.int

floatDecoder : Decoder Float
floatDecoder = Decode.float

boolDecoder : Decoder Bool
boolDecoder = Decode.bool

-- Field extraction
nameDecoder : Decoder String
nameDecoder =
    Decode.field "name" Decode.string

-- Nested fields
streetDecoder : Decoder String
streetDecoder =
    Decode.at ["address", "street"] Decode.string
```

**Complex Decoders:**
```elm
type alias User =
    { id : Int
    , name : String
    , email : String
    , age : Maybe Int
    , isActive : Bool
    }

userDecoder : Decoder User
userDecoder =
    Decode.map5 User
        (Decode.field "id" Decode.int)
        (Decode.field "name" Decode.string)
        (Decode.field "email" Decode.string)
        (Decode.maybe (Decode.field "age" Decode.int))
        (Decode.field "is_active" Decode.bool)

-- Alternative with pipeline style (requires elm-json-decode-pipeline)
userDecoderPipeline : Decoder User
userDecoderPipeline =
    Decode.succeed User
        |> required "id" Decode.int
        |> required "name" Decode.string
        |> required "email" Decode.string
        |> optional "age" (Decode.maybe Decode.int) Nothing
        |> required "is_active" Decode.bool
```

**List and Array Decoders:**
```elm
-- Decode list of users
usersDecoder : Decoder (List User)
usersDecoder =
    Decode.list userDecoder

-- Decode field containing list
postsDecoder : Decoder (List Post)
postsDecoder =
    Decode.field "posts" (Decode.list postDecoder)

-- Decode dictionary
userByIdDecoder : Decoder (Dict String User)
userByIdDecoder =
    Decode.dict userDecoder
```

**Handling Decode Errors:**
```elm
type Msg
    = GotData (Result Http.Error Data)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        GotData result ->
            case result of
                Ok data ->
                    ( { model | data = Just data }
                    , Cmd.none
                    )

                Err httpError ->
                    case httpError of
                        Http.BadUrl url ->
                            ( { model | error = Just ("Bad URL: " ++ url) }
                            , Cmd.none
                            )

                        Http.Timeout ->
                            ( { model | error = Just "Request timeout" }
                            , Cmd.none
                            )

                        Http.NetworkError ->
                            ( { model | error = Just "Network error" }
                            , Cmd.none
                            )

                        Http.BadStatus code ->
                            ( { model | error = Just ("Bad status: " ++ String.fromInt code) }
                            , Cmd.none
                            )

                        Http.BadBody message ->
                            ( { model | error = Just ("Decode error: " ++ message) }
                            , Cmd.none
                            )
```

## JavaScript Interop

### Flags (Initial Data)

Pass data from JavaScript to Elm on initialization:

**Elm Side:**
```elm
type alias Flags =
    { apiKey : String
    , userId : Int
    , theme : String
    }

init : Flags -> (Model, Cmd Msg)
init flags =
    ( { apiKey = flags.apiKey
      , userId = flags.userId
      , theme = flags.theme
      , data = Nothing
      }
    , fetchUserData flags.userId
    )

main : Program Flags Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```

**JavaScript Side:**
```javascript
var app = Elm.Main.init({
    node: document.getElementById('app'),
    flags: {
        apiKey: 'your-api-key',
        userId: 12345,
        theme: 'dark'
    }
});
```

### Ports (Bidirectional Communication)

**Defining Ports:**
```elm
port module Main exposing (..)

-- Port for sending data to JavaScript
port saveToLocalStorage : String -> Cmd msg

-- Port for receiving data from JavaScript
port onStorageChange : (String -> msg) -> Sub msg

-- Using ports in update
type Msg
    = SaveData String
    | StorageChanged String

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        SaveData data ->
            ( model
            , saveToLocalStorage data
            )

        StorageChanged data ->
            ( { model | data = data }
            , Cmd.none
            )

-- Using ports in subscriptions
subscriptions : Model -> Sub Msg
subscriptions model =
    onStorageChange StorageChanged
```

**JavaScript Side:**
```javascript
var app = Elm.Main.init({
    node: document.getElementById('app')
});

// Receiving from Elm
app.ports.saveToLocalStorage.subscribe(function(data) {
    localStorage.setItem('app-data', data);
});

// Sending to Elm
window.addEventListener('storage', function(e) {
    if (e.key === 'app-data') {
        app.ports.onStorageChange.send(e.newValue);
    }
});
```

**Common Port Patterns:**
```elm
-- Local storage
port setStor