Agent skill
convert-haskell-elm
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.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/convert-haskell-elm
SKILL.md
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
| Haskell | Elm | Notes |
|---|---|---|
String |
String |
Direct mapping |
Int |
Int |
Direct mapping |
Float / Double |
Float |
Elm has single float type |
Bool |
Bool |
Direct mapping |
[a] |
List a |
Direct mapping |
(a, b) |
(a, b) |
Tuples identical |
Maybe a |
Maybe a |
Direct mapping |
Either a b |
Result a b |
Similar but swapped order |
data X = A | B |
type X = A | B |
Union types |
newtype X = X a |
type X = X a |
Custom types |
type X = Y |
type alias X = Y |
Type aliases |
IO a |
Cmd msg |
Effects via TEA |
map |
List.map |
Core library |
fmap / <$> |
Maybe.map |
Per-type functions |
>>= |
Maybe.andThen |
Per-type, no do-notation |
When Converting Code
- Identify pure logic - Elm can only run in browser (frontend focus)
- Map types first - Haskell and Elm types are very similar
- Convert IO/State to TEA - Effects become Cmd, state becomes Model
- Preserve semantics - Both are pure functional languages
- Simplify advanced features - Elm deliberately limits language complexity
- Test equivalence - Property-based tests translate well
Type System Mapping
Primitive Types
| Haskell | Elm | Notes |
|---|---|---|
Int |
Int |
Direct mapping |
Integer |
- | Arbitrary precision not in Elm; use Int |
Float |
Float |
Single float type in Elm |
Double |
Float |
Map to Elm's Float |
Char |
Char |
Direct mapping |
String |
String |
Both are lists of Char conceptually |
Bool |
Bool |
Direct mapping |
() |
() |
Unit type identical |
Collection Types
| Haskell | Elm | Notes |
|---|---|---|
[a] |
List a |
Direct mapping |
(a, b) |
(a, b) |
Tuples up to 3 elements |
(a, b, c) |
(a, b, c) |
Maximum 3-tuple in Elm |
Data.Map k v |
Dict k v |
Dict in Elm requires comparable k |
Data.Set a |
Set a |
Set in Elm requires comparable a |
Data.Array a |
Array a |
Similar, but Elm's is more limited |
Data.Text |
String |
Elm String is the standard |
Composite Types
| Haskell | Elm | Notes |
|---|---|---|
data X = A | B |
type X = A | B |
Union types (custom types in Elm) |
data X = X Int String |
type X = X Int String |
Constructor with data |
newtype X = X Int |
type X = X Int |
Single-constructor type |
type X = Int |
type alias X = Int |
Type 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
| Haskell | Elm | Notes |
|---|---|---|
Maybe a |
Maybe a |
Identical |
Just x |
Just x |
Identical |
Nothing |
Nothing |
Identical |
Either a b |
Result a b |
Order swapped: Either err ok → Result err ok |
Left err |
Err err |
Error case |
Right ok |
Ok ok |
Success case |
Function Types
| Haskell | Elm | Notes |
|---|---|---|
a -> b |
a -> b |
Function type identical |
a -> b -> c |
a -> b -> c |
Currying identical |
(a -> b) -> c |
(a -> b) -> c |
Higher-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
.fieldsyntax in Elm - No do-notation in Elm; use
Maybe.andThenfor 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 headandtailreturn Maybe in Elm (safer)- Pattern matching on lists is identical
- Elm uses
modByinstead ofmod
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
databecomes Elmtypefor union types - Haskell records become Elm
type aliaswith 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
caseexpressions 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(+) 5in 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
typebecomes Elmtype alias - Haskell
newtypebecomes Elmtype(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 bbecomesResult a b(same order)LeftbecomesErr,RightbecomesOk- No do-notation in Elm; use
Result.andThenfor chaining Result.mapandResult.andThenreplace 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.batchsends 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
| Task | Haskell | Elm | Notes |
|---|---|---|---|
| Build | cabal build / stack build |
elm make |
Elm is simpler |
| REPL | ghci |
elm repl |
Similar experience |
| Format | brittany / ormolu |
elm-format |
Elm format is standard |
| Test | hspec / QuickCheck |
elm-test |
Property tests in both |
| Lint | hlint |
elm-review |
Elm-review is powerful |
| Docs | Haddock | elm-doc-preview |
Elm 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 exampleslang-haskell-dev- Haskell development patternslang-elm-dev- Elm development patterns and The Elm Architecturepatterns-concurrency-dev- Compare IO/STM to Elm's Cmd/Subpatterns-serialization-dev- JSON handling across languages
Didn't find tool you were looking for?