Back to list
aRustyDev

convert-haskell-elm

by aRustyDev

AI Contexts, Commands, Plugins, Docs, Examples, Specs, Etc

1🍴 1📅 Jan 16, 2026

SKILL.md


name: convert-haskell-elm description: Convert Haskell code to idiomatic Elm. Use when migrating Haskell logic to frontend applications, translating pure functional patterns to Elm's architecture, or refactoring Haskell code for web UI. Extends meta-convert-dev with Haskell-to-Elm specific patterns.

Convert Haskell to Elm

Convert Haskell code to idiomatic Elm. This skill extends meta-convert-dev with Haskell-to-Elm specific type mappings, idiom translations, and The Elm Architecture integration.

This Skill Extends

  • meta-convert-dev - Foundational conversion patterns (APTV workflow, testing strategies)

For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.

This Skill Adds

  • Type mappings: Haskell types → Elm types
  • Idiom translations: Haskell patterns → Elm idioms
  • TEA integration: Pure functions → Model-View-Update pattern
  • Effect handling: IO/State monads → Cmd/Sub in Elm
  • JSON handling: Aeson patterns → Elm decoders/encoders

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Haskell language fundamentals - see lang-haskell-dev
  • Elm language fundamentals - see lang-elm-dev
  • Reverse conversion (Elm → Haskell) - see convert-elm-haskell
  • Advanced Haskell features (GADTs, Type Families) - no Elm equivalent
  • Backend-specific Haskell code - focus on pure logic convertible to frontend

Quick Reference

HaskellElmNotes
StringStringDirect mapping
IntIntDirect mapping
Float / DoubleFloatElm has single float type
BoolBoolDirect mapping
[a]List aDirect mapping
(a, b)(a, b)Tuples identical
Maybe aMaybe aDirect mapping
Either a bResult a bSimilar but swapped order
data X = A | Btype X = A | BUnion types
newtype X = X atype X = X aCustom types
type X = Ytype alias X = YType aliases
IO aCmd msgEffects via TEA
mapList.mapCore library
fmap / <$>Maybe.mapPer-type functions
>>=Maybe.andThenPer-type, no do-notation

When Converting Code

  1. Identify pure logic - Elm can only run in browser (frontend focus)
  2. Map types first - Haskell and Elm types are very similar
  3. Convert IO/State to TEA - Effects become Cmd, state becomes Model
  4. Preserve semantics - Both are pure functional languages
  5. Simplify advanced features - Elm deliberately limits language complexity
  6. Test equivalence - Property-based tests translate well

Type System Mapping

Primitive Types

HaskellElmNotes
IntIntDirect mapping
Integer-Arbitrary precision not in Elm; use Int
FloatFloatSingle float type in Elm
DoubleFloatMap to Elm's Float
CharCharDirect mapping
StringStringBoth are lists of Char conceptually
BoolBoolDirect mapping
()()Unit type identical

Collection Types

HaskellElmNotes
[a]List aDirect mapping
(a, b)(a, b)Tuples up to 3 elements
(a, b, c)(a, b, c)Maximum 3-tuple in Elm
Data.Map k vDict k vDict in Elm requires comparable k
Data.Set aSet aSet in Elm requires comparable a
Data.Array aArray aSimilar, but Elm's is more limited
Data.TextStringElm String is the standard

Composite Types

HaskellElmNotes
data X = A | Btype X = A | BUnion types (custom types in Elm)
data X = X Int Stringtype X = X Int StringConstructor with data
newtype X = X Inttype X = X IntSingle-constructor type
type X = Inttype alias X = IntType alias
data X = X { f :: Int }type alias X = { f : Int }Records use type alias in Elm
Type class-No type classes in Elm

Maybe and Result

HaskellElmNotes
Maybe aMaybe aIdentical
Just xJust xIdentical
NothingNothingIdentical
Either a bResult a bOrder swapped: Either err ok → Result err ok
Left errErr errError case
Right okOk okSuccess case

Function Types

HaskellElmNotes
a -> ba -> bFunction type identical
a -> b -> ca -> b -> cCurrying identical
(a -> b) -> c(a -> b) -> cHigher-order functions
Type class constraints-No constraints in Elm

Idiom Translation

Pattern 1: Maybe Handling

Haskell:

findUser :: Int -> Maybe User
findUser id = lookup id users

displayName :: Maybe User -> String
displayName maybeUser = case maybeUser of
    Just user -> name user
    Nothing -> "Anonymous"

-- Using fmap
getName :: Maybe User -> Maybe String
getName = fmap name

-- Using bind
getUserEmail :: Int -> Maybe String
getUserEmail userId = do
    user <- findUser userId
    return (email user)

Elm:

findUser : Int -> Maybe User
findUser id =
    Dict.get id users

displayName : Maybe User -> String
displayName maybeUser =
    case maybeUser of
        Just user ->
            user.name

        Nothing ->
            "Anonymous"

-- Using Maybe.map (equivalent to fmap)
getName : Maybe User -> Maybe String
getName =
    Maybe.map .name

-- Using Maybe.andThen (equivalent to >>=)
getUserEmail : Int -> Maybe String
getUserEmail userId =
    findUser userId
        |> Maybe.map .email

Why this translation:

  • Both languages have identical Maybe type
  • Elm uses pipeline operator |> instead of do-notation
  • Record access uses .field syntax in Elm
  • No do-notation in Elm; use Maybe.andThen for chaining

Pattern 2: List Operations

Haskell:

-- List comprehension
evens :: [Int]
evens = [x | x <- [1..10], even x]

-- Map, filter, fold
processNumbers :: [Int] -> Int
processNumbers nums = foldr (+) 0 $ map (*2) $ filter (>0) nums

-- Pattern matching on lists
listLength :: [a] -> Int
listLength [] = 0
listLength (_:xs) = 1 + listLength xs

-- List functions
result = take 5 [1..10]
result = drop 3 [1..10]
result = head [1,2,3]
result = tail [1,2,3]

Elm:

-- No list comprehension; use functions
evens : List Int
evens =
    List.range 1 10
        |> List.filter (\x -> modBy 2 x == 0)

-- Map, filter, fold (same pattern)
processNumbers : List Int -> Int
processNumbers nums =
    nums
        |> List.filter (\x -> x > 0)
        |> List.map (\x -> x * 2)
        |> List.foldl (+) 0

-- Pattern matching on lists (identical)
listLength : List a -> Int
listLength list =
    case list of
        [] ->
            0

        _ :: xs ->
            1 + listLength xs

-- List functions (similar)
result = List.take 5 (List.range 1 10)
result = List.drop 3 (List.range 1 10)
result = List.head [1, 2, 3]  -- Returns Maybe a
result = List.tail [1, 2, 3]  -- Returns Maybe (List a)

Why this translation:

  • No list comprehensions in Elm; use filter/map
  • Pipeline operator |> for readability
  • head and tail return Maybe in Elm (safer)
  • Pattern matching on lists is identical
  • Elm uses modBy instead of mod

Pattern 3: Custom Types (ADTs)

Haskell:

-- Simple sum type
data Shape = Circle Float
           | Rectangle Float Float
           | Triangle Float Float Float

area :: Shape -> Float
area (Circle r) = pi * r^2
area (Rectangle w h) = w * h
area (Triangle a b c) =
    let s = (a + b + c) / 2
    in sqrt (s * (s-a) * (s-b) * (s-c))

-- Type with records
data Person = Person
    { firstName :: String
    , lastName :: String
    , age :: Int
    } deriving (Show, Eq)

fullName :: Person -> String
fullName person = firstName person ++ " " ++ lastName person

Elm:

-- Simple union type
type Shape
    = Circle Float
    | Rectangle Float Float
    | Triangle Float Float Float

area : Shape -> Float
area shape =
    case shape of
        Circle r ->
            pi * r ^ 2

        Rectangle w h ->
            w * h

        Triangle a b c ->
            let
                s =
                    (a + b + c) / 2
            in
            sqrt (s * (s - a) * (s - b) * (s - c))

-- Type with records (use type alias)
type alias Person =
    { firstName : String
    , lastName : String
    , age : Int
    }

fullName : Person -> String
fullName person =
    person.firstName ++ " " ++ person.lastName

Why this translation:

  • Haskell data becomes Elm type for union types
  • Haskell records become Elm type alias with record
  • No automatic deriving in Elm
  • Pattern matching is nearly identical
  • Record field access uses dot notation in Elm

Pattern 4: Recursive Functions

Haskell:

-- Factorial
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

-- Fibonacci
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- Map implementation
map' :: (a -> b) -> [a] -> [b]
map' _ [] = []
map' f (x:xs) = f x : map' f xs

-- Fold implementation
foldr' :: (a -> b -> b) -> b -> [a] -> b
foldr' _ acc [] = acc
foldr' f acc (x:xs) = f x (foldr' f acc xs)

Elm:

-- Factorial
factorial : Int -> Int
factorial n =
    case n of
        0 ->
            1

        _ ->
            n * factorial (n - 1)

-- Fibonacci
fib : Int -> Int
fib n =
    case n of
        0 ->
            0

        1 ->
            1

        _ ->
            fib (n - 1) + fib (n - 2)

-- Map implementation
map_ : (a -> b) -> List a -> List b
map_ f list =
    case list of
        [] ->
            []

        x :: xs ->
            f x :: map_ f xs

-- Fold implementation
foldr_ : (a -> b -> b) -> b -> List a -> b
foldr_ f acc list =
    case list of
        [] ->
            acc

        x :: xs ->
            f x (foldr_ f acc xs)

Why this translation:

  • Elm doesn't support function pattern matching directly
  • Use case expressions for pattern matching in Elm
  • List cons operator :: is identical
  • Recursion patterns are the same

Pattern 5: Higher-Order Functions

Haskell:

-- Function composition
addThenDouble :: Int -> Int
addThenDouble = (*2) . (+1)

-- Partial application
add5 :: Int -> Int
add5 = (+5)

-- Map and filter composition
process :: [Int] -> [Int]
process = filter even . map (*2)

-- Lambda functions
square = \x -> x * x

-- Using $ to avoid parentheses
result = show $ sum $ map (*2) [1,2,3]

Elm:

-- Function composition
addThenDouble : Int -> Int
addThenDouble =
    (+) 1 >> (*) 2

-- Partial application
add5 : Int -> Int
add5 =
    (+) 5

-- Map and filter composition
process : List Int -> List Int
process =
    List.map ((*) 2) >> List.filter (\x -> modBy 2 x == 0)

-- Lambda functions (identical)
square =
    \x -> x * x

-- Using |> and <| instead of $
result =
    [1, 2, 3]
        |> List.map ((*) 2)
        |> List.sum
        |> String.fromInt

Why this translation:

  • Elm uses >> for left-to-right composition (vs . in Haskell)
  • Elm uses << for right-to-left composition (like Haskell's .)
  • Pipeline operator |> replaces many uses of $
  • Operator sections work differently; (+5) becomes (+) 5 in Elm

Pattern 6: Type Aliases vs Newtypes

Haskell:

-- Type alias
type UserId = Int
type Email = String

-- Newtype for type safety
newtype UserId = UserId Int deriving (Show, Eq)
newtype Email = Email String deriving (Show, Eq)

getUserById :: UserId -> Maybe User
getUserById (UserId id) = lookup id users

-- Can't mix UserId and Email

Elm:

-- Type alias (no type safety)
type alias UserId =
    Int

type alias Email =
    String

-- Custom type for type safety
type UserId
    = UserId Int

type Email
    = Email String

getUserById : UserId -> Maybe User
getUserById (UserId id) =
    Dict.get id users

-- Can't mix UserId and Email (type safety enforced)

Why this translation:

  • Haskell type becomes Elm type alias
  • Haskell newtype becomes Elm type (custom type)
  • Both provide type safety at compile time
  • Elm custom types have zero runtime cost (like newtype)

Error Handling

Haskell Either → Elm Result

Haskell:

type Error = String

parseAge :: String -> Either Error Int
parseAge str = case reads str of
    [(n, "")] -> if n >= 0
                 then Right n
                 else Left "Age must be non-negative"
    _ -> Left "Not a valid number"

validateUser :: String -> String -> Either Error User
validateUser ageStr emailStr = do
    age <- parseAge ageStr
    email <- validateEmail emailStr
    return $ User email age

-- Using either
displayResult :: Either Error User -> String
displayResult = either ("Error: " ++) (show . userId)

Elm:

type alias Error =
    String

parseAge : String -> Result Error Int
parseAge str =
    case String.toInt str of
        Just n ->
            if n >= 0 then
                Ok n
            else
                Err "Age must be non-negative"

        Nothing ->
            Err "Not a valid number"

validateUser : String -> String -> Result Error User
validateUser ageStr emailStr =
    parseAge ageStr
        |> Result.andThen (\age ->
            validateEmail emailStr
                |> Result.map (\email ->
                    User email age
                )
        )

-- Using Result.withDefault or case
displayResult : Result Error User -> String
displayResult result =
    case result of
        Ok user ->
            String.fromInt user.userId

        Err error ->
            "Error: " ++ error

Why this translation:

  • Either a b becomes Result a b (same order)
  • Left becomes Err, Right becomes Ok
  • No do-notation in Elm; use Result.andThen for chaining
  • Result.map and Result.andThen replace fmap and >>=

Effect Handling: IO/State → The Elm Architecture

IO Actions → Cmd

Haskell:

-- IO actions
main :: IO ()
main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn $ "Hello, " ++ name

-- HTTP request (using simple-http)
fetchUser :: Int -> IO (Either Error User)
fetchUser userId = do
    response <- httpGet $ "/users/" ++ show userId
    return $ decodeUser response

Elm:

-- Commands in TEA
type Msg
    = NameEntered String
    | FetchUser Int
    | GotUser (Result Http.Error User)

-- No IO monad; effects via Cmd
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NameEntered name ->
            ( { model | name = name }, Cmd.none )

        FetchUser userId ->
            ( model, fetchUser userId )

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

                Err error ->
                    ( { model | error = Just error }, Cmd.none )

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

Why this translation:

  • Haskell IO becomes Elm Cmd
  • No imperative sequencing in Elm
  • Effects handled by The Elm Architecture runtime
  • State updates and commands returned together as tuple

State Monad → Model

Haskell:

import Control.Monad.State

type Counter a = State Int a

increment :: Counter ()
increment = modify (+1)

getCount :: Counter Int
getCount = get

computation :: Counter Int
computation = do
    increment
    increment
    count <- getCount
    return count

-- Run state
result = runState computation 0  -- (2, 2)

Elm:

-- No State monad; use Model in TEA
type alias Model =
    { count : Int
    }

type Msg
    = Increment
    | GetCount

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Increment ->
            ( { model | count = model.count + 1 }, Cmd.none )

        GetCount ->
            -- In Elm, view always has access to model
            -- No need for separate "get" operation
            ( model, Cmd.none )

-- Model updates are explicit in update function
-- No hidden state threading

Why this translation:

  • State monad patterns become Model updates
  • Explicit state passing via Model in update function
  • No monad; state is first-class in TEA
  • All state changes visible in update

JSON Handling

Aeson → Elm Decoders

Haskell:

{-# LANGUAGE DeriveGeneric #-}

import Data.Aeson
import GHC.Generics

data User = User
    { name :: String
    , email :: String
    , age :: Int
    } deriving (Generic, Show)

instance FromJSON User
instance ToJSON User

-- Decode JSON
decodeUser :: ByteString -> Either String User
decodeUser = eitherDecode

-- Encode JSON
encodeUser :: User -> ByteString
encodeUser = encode

Elm:

import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode

type alias User =
    { name : String
    , email : String
    , age : Int
    }

-- Decoder (explicit, no deriving)
userDecoder : Decoder User
userDecoder =
    Decode.map3 User
        (Decode.field "name" Decode.string)
        (Decode.field "email" Decode.string)
        (Decode.field "age" Decode.int)

-- Encoder (explicit)
encodeUser : User -> Encode.Value
encodeUser user =
    Encode.object
        [ ( "name", Encode.string user.name )
        , ( "email", Encode.string user.email )
        , ( "age", Encode.int user.age )
        ]

-- Decode JSON string
decodeUser : String -> Result Decode.Error User
decodeUser jsonString =
    Decode.decodeString userDecoder jsonString

Why this translation:

  • No automatic deriving in Elm
  • Decoders are explicit and composable
  • Elm decoders fail at first error (like Aeson)
  • Encoders are straightforward value constructors

Concurrency Patterns

Haskell Async → Elm Cmd.batch

Haskell:

import Control.Concurrent.Async

-- Run multiple IO actions concurrently
fetchMultiple :: IO (User, Orders)
fetchMultiple = do
    (user, orders) <- concurrently fetchUser fetchOrders
    return (user, orders)

-- With mapConcurrently
fetchAllUsers :: [UserId] -> IO [User]
fetchAllUsers = mapConcurrently fetchUser

Elm:

-- Commands execute concurrently (managed by runtime)
type Msg
    = GotUser (Result Http.Error User)
    | GotOrders (Result Http.Error (List Order))

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        StartFetching ->
            ( { model | loading = True }
            , Cmd.batch
                [ Http.get { url = "/user", expect = Http.expectJson GotUser userDecoder }
                , Http.get { url = "/orders", expect = Http.expectJson GotOrders ordersDecoder }
                ]
            )

        GotUser result ->
            -- Handle user result
            ( handleUserResult result model, Cmd.none )

        GotOrders result ->
            -- Handle orders result
            ( handleOrdersResult result model, Cmd.none )

-- Multiple requests
fetchAllUsers : List Int -> Cmd Msg
fetchAllUsers userIds =
    userIds
        |> List.map (\id -> Http.get { url = "/users/" ++ String.fromInt id, ... })
        |> Cmd.batch

Why this translation:

  • Cmd.batch sends multiple commands
  • Elm runtime manages concurrency
  • Each response handled independently via Msg
  • No explicit async/await or threads

Common Pitfalls

1. No Type Classes

Problem: Trying to use type class polymorphism

-- Haskell: type classes
show :: Show a => a -> String
(==) :: Eq a => a -> a -> Bool

Solution: Use concrete types or phantom types

-- Elm: No type classes, use concrete functions
String.fromInt : Int -> String
String.fromFloat : Float -> String

-- Equality works only on comparable types
(==) : comparable -> comparable -> Bool

-- For custom types, write explicit functions
showUser : User -> String
showUser user =
    user.name ++ " (" ++ String.fromInt user.age ++ ")"

2. No Do-Notation

Problem: Trying to use do-notation

-- Haskell
getUserEmail :: Int -> Maybe String
getUserEmail userId = do
    user <- findUser userId
    return (email user)

Solution: Use andThen and pipelines

-- Elm
getUserEmail : Int -> Maybe String
getUserEmail userId =
    findUser userId
        |> Maybe.map .email

-- For complex chains
validateAndCreate : Form -> Result Error User
validateAndCreate form =
    validateEmail form.email
        |> Result.andThen (\email ->
            validateAge form.ageStr
                |> Result.map (\age ->
                    User email age
                )
        )

3. No Lazy Evaluation by Default

Problem: Assuming infinite lists

-- Haskell: infinite lists work
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
take 10 fibs  -- [0,1,1,2,3,5,8,13,21,34]

Solution: Generate finite lists

-- Elm: Must be finite
fibs : Int -> List Int
fibs n =
    fibsHelper n [0, 1]

fibsHelper : Int -> List Int -> List Int
fibsHelper remaining acc =
    if remaining <= 0 then
        List.reverse acc
    else
        case acc of
            x :: y :: _ ->
                fibsHelper (remaining - 1) (x + y :: acc)

            _ ->
                acc

-- Or use recursion with explicit limit
take10Fibs = fibs 10

4. Different Operator Precedence

Problem: Assuming Haskell operator behavior

-- Haskell
result = f $ g $ h x  -- Right associative
composed = f . g . h  -- Function composition

Solution: Use Elm operators correctly

-- Elm
result =
    x
        |> h
        |> g
        |> f

-- Or use <|
result = f <| g <| h x

-- Function composition
composed = f << g << h  -- Right-to-left (like Haskell .)
composed = h >> g >> f  -- Left-to-right (more intuitive)

5. No Arbitrary Type Constructors in Type Aliases

Problem: Using higher-kinded types

-- Haskell
type Container f a = f a

Solution: Use concrete types

-- Elm: No higher-kinded types
type alias MaybeContainer a =
    Maybe a

type alias ListContainer a =
    List a

-- Can't abstract over the container type

Tooling

TaskHaskellElmNotes
Buildcabal build / stack buildelm makeElm is simpler
REPLghcielm replSimilar experience
Formatbrittany / ormoluelm-formatElm format is standard
Testhspec / QuickCheckelm-testProperty tests in both
Linthlintelm-reviewElm-review is powerful
DocsHaddockelm-doc-previewElm docs are interactive

Examples

Example 1: Simple - Maybe and Pattern Matching

Before (Haskell):

data User = User { name :: String, age :: Int }

findUser :: Int -> Maybe User
findUser 1 = Just (User "Alice" 30)
findUser _ = Nothing

greetUser :: Int -> String
greetUser userId = case findUser userId of
    Just user -> "Hello, " ++ name user
    Nothing -> "User not found"

After (Elm):

type alias User =
    { name : String
    , age : Int
    }

findUser : Int -> Maybe User
findUser userId =
    if userId == 1 then
        Just { name = "Alice", age = 30 }
    else
        Nothing

greetUser : Int -> String
greetUser userId =
    case findUser userId of
        Just user ->
            "Hello, " ++ user.name

        Nothing ->
            "User not found"

Example 2: Medium - List Processing and Result

Before (Haskell):

validateAge :: Int -> Either String Int
validateAge age
    | age < 0 = Left "Age cannot be negative"
    | age > 150 = Left "Age too high"
    | otherwise = Right age

processAges :: [Int] -> Either String [Int]
processAges ages = mapM validateAge $ filter (> 0) ages

computeTotal :: Either String [Int] -> Int
computeTotal result = case result of
    Right ages -> sum ages
    Left _ -> 0

After (Elm):

validateAge : Int -> Result String Int
validateAge age =
    if age < 0 then
        Err "Age cannot be negative"
    else if age > 150 then
        Err "Age too high"
    else
        Ok age

processAges : List Int -> Result String (List Int)
processAges ages =
    ages
        |> List.filter (\a -> a > 0)
        |> List.map validateAge
        |> combineResults

combineResults : List (Result e a) -> Result e (List a)
combineResults results =
    List.foldr (Result.map2 (::)) (Ok []) results

computeTotal : Result String (List Int) -> Int
computeTotal result =
    case result of
        Ok ages ->
            List.sum ages

        Err _ ->
            0

Example 3: Complex - The Elm Architecture Integration

Before (Haskell - Pure Logic):

data TodoItem = TodoItem
    { itemId :: Int
    , text :: String
    , completed :: Bool
    }

data TodoList = TodoList
    { items :: [TodoItem]
    , nextId :: Int
    }

addTodo :: String -> TodoList -> TodoList
addTodo text list = TodoList
    { items = newItem : items list
    , nextId = nextId list + 1
    }
  where
    newItem = TodoItem (nextId list) text False

toggleTodo :: Int -> TodoList -> TodoList
toggleTodo targetId list = list
    { items = map toggleIfMatch (items list) }
  where
    toggleIfMatch item =
        if itemId item == targetId
        then item { completed = not (completed item) }
        else item

filterTodos :: (TodoItem -> Bool) -> TodoList -> [TodoItem]
filterTodos predicate = filter predicate . items

After (Elm - Full TEA Application):

-- MODEL

type alias TodoItem =
    { itemId : Int
    , text : String
    , completed : Bool
    }

type alias Model =
    { items : List TodoItem
    , nextId : Int
    , inputText : String
    , filter : Filter
    }

type Filter
    = All
    | Active
    | Completed

init : Model
init =
    { items = []
    , nextId = 1
    , inputText = ""
    , filter = All
    }

-- UPDATE

type Msg
    = UpdateInput String
    | AddTodo
    | ToggleTodo Int
    | SetFilter Filter

update : Msg -> Model -> Model
update msg model =
    case msg of
        UpdateInput text ->
            { model | inputText = text }

        AddTodo ->
            if String.isEmpty model.inputText then
                model
            else
                { model
                    | items =
                        { itemId = model.nextId
                        , text = model.inputText
                        , completed = False
                        }
                            :: model.items
                    , nextId = model.nextId + 1
                    , inputText = ""
                }

        ToggleTodo targetId ->
            { model
                | items =
                    List.map
                        (\item ->
                            if item.itemId == targetId then
                                { item | completed = not item.completed }
                            else
                                item
                        )
                        model.items
            }

        SetFilter filter ->
            { model | filter = filter }

-- VIEW

view : Model -> Html Msg
view model =
    div []
        [ input
            [ placeholder "What needs to be done?"
            , value model.inputText
            , onInput UpdateInput
            ]
            []
        , button [ onClick AddTodo ] [ text "Add" ]
        , div []
            [ button [ onClick (SetFilter All) ] [ text "All" ]
            , button [ onClick (SetFilter Active) ] [ text "Active" ]
            , button [ onClick (SetFilter Completed) ] [ text "Completed" ]
            ]
        , ul [] (List.map viewTodoItem (filteredItems model))
        ]

filteredItems : Model -> List TodoItem
filteredItems model =
    case model.filter of
        All ->
            model.items

        Active ->
            List.filter (\item -> not item.completed) model.items

        Completed ->
            List.filter .completed model.items

viewTodoItem : TodoItem -> Html Msg
viewTodoItem item =
    li
        [ onClick (ToggleTodo item.itemId)
        , style "text-decoration"
            (if item.completed then
                "line-through"
             else
                "none"
            )
        ]
        [ text item.text ]

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational patterns with cross-language examples
  • lang-haskell-dev - Haskell development patterns
  • lang-elm-dev - Elm development patterns and The Elm Architecture
  • patterns-concurrency-dev - Compare IO/STM to Elm's Cmd/Sub
  • patterns-serialization-dev - JSON handling across languages

Score

Total Score

55/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

0/5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon