Bidirectional conversion between Elm and Haskell. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Elm↔Haskell specific patterns. Use when migrating Elm frontend code to Haskell, translating Elm patterns to idiomatic Haskell, or refactoring Elm codebases. Extends meta-convert-dev with Elm-to-Haskell specific patterns.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Bidirectional conversion between Elm and Haskell. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Elm↔Haskell specific patterns. Use when migrating Elm frontend code to Haskell, translating Elm patterns to idiomatic Haskell, or refactoring Elm codebases. Extends meta-convert-dev with Elm-to-Haskell specific patterns.
Elm ↔ Haskell Conversion
Bidirectional conversion between Elm and Haskell. This skill extends meta-convert-dev with Elm↔Haskell specific type mappings, idiom translations, and tooling for migrating functional frontend code to backend or library code.
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: Elm types → Haskell types
Idiom translations: The Elm Architecture (TEA) → Haskell patterns
Error handling: Elm Maybe/Result → Haskell Maybe/Either
Async patterns: Elm Cmd/Sub → Haskell IO/concurrency
JSON handling: Elm decoders/encoders → Aeson
This Skill Does NOT Cover
General conversion methodology - see meta-convert-dev
Elm language fundamentals - see lang-elm-dev
Haskell language fundamentals - see lang-haskell-dev
Frontend frameworks - Elm's TEA is frontend-specific; backend alternatives vary
Quick Reference
Elm
Haskell
Notes
String
String or Text
Use Text for production
Int
Int or Integer
Integer for unbounded
Float
Double
Default floating point
Bool
Bool
Direct mapping
List a
[a]
Direct mapping
Maybe a
Maybe a
Same type!
Result err ok
Either err ok
Swap order: Either Left Right
type alias
type or data with record
Similar syntax
type (union)
data
Same concept, similar syntax
Cmd msg
IO ()
Side effects
Sub msg
Event sources
Streams, STM, async
Html msg
No direct equivalent
Frontend-specific
Json.Decode.Decoder a
FromJSON a
Aeson instance
Json.Encode.Value
ToJSON a
Aeson instance
When Converting Code
Analyze source thoroughly before writing target
Map types first - Elm and Haskell are very similar
Preserve semantics over syntax similarity
Adapt TEA patterns - No direct equivalent; rethink architecture
Handle Cmd/Sub - Map to IO, concurrency, or events
Test equivalence - Same inputs → same outputs for pure logic
Type System Mapping
Primitive Types
Elm
Haskell
Notes
String
String
List of Char (inefficient)
String
Text
Preferred for production (from Data.Text)
Int
Int
Bounded integer (architecture-dependent)
Int
Integer
Unbounded (arbitrary precision)
Float
Float
Single precision
Float
Double
Preferred double precision
Bool
Bool
Direct mapping
()
()
Unit type
Never
-
No direct equivalent; use polymorphic types
Collection Types
Elm
Haskell
Notes
List a
[a]
Identical linked list
Array a
Vector a
Use Data.Vector for efficient arrays
Set a
Set a
Use Data.Set
Dict k v
Map k v
Use Data.Map
( a, b )
(a, b)
Tuple - identical
( a, b, c )
(a, b, c)
Tuples up to any size
Composite Types
Elm
Haskell
Notes
type alias User = { name : String }
data User = User { name :: String }
Record syntax
type Msg = Click | Input String
data Msg = Click | Input String
Union type / sum type
Maybe a
Maybe a
Identical
Result err ok
Either err ok
Same concept, different order (Left = error, Right = success)
Type Aliases vs Data
Elm:
-- Type alias: Just a synonym
type alias Point = { x : Float, y : Float }
-- Custom type: New type with constructors
type Shape = Circle Float | Rectangle Float Float
Haskell:
-- Type synonym: Just an alias (no constructor)
type Point = (Double, Double)
-- Or with record syntax (creates constructor and accessors)
data Point = Point { x :: Double, y :: Double }
-- Custom type: Algebraic data type
data Shape = Circle Double | Rectangle Double Double
Why this translation:
Elm's type alias for records creates a constructor; Haskell type does not
Use Haskell data with record syntax for Elm record type aliases
Union types map directly to Haskell algebraic data types
Idiom Translation
Pattern 1: Maybe Handling
Elm:
findUser : Int -> Maybe User
findUser id =
List.head (List.filter (\u -> u.id == id) users)
displayName : Maybe User -> String
displayName maybeUser =
case maybeUser of
Just user ->
user.name
Nothing ->
"Anonymous"
-- With pipeline
name =
findUser 1
|> Maybe.map .name
|> Maybe.withDefault "Anonymous"
Haskell:
import Data.Maybe (fromMaybe, maybe, listToMaybe)
import Data.List (find)
findUser :: Int -> Maybe User
findUser userId = find (\u -> userId == id u) users
displayName :: Maybe User -> String
displayName maybeUser =
case maybeUser of
Just user -> name user
Nothing -> "Anonymous"
-- With applicative/functor
userName :: Maybe String
userName = name <$> findUser 1
userName' :: String
userName' = fromMaybe "Anonymous" (name <$> findUser 1)
Why this translation:
Maybe is identical in both languages
Elm's Maybe.withDefault = Haskell's fromMaybe
Elm's .field accessor becomes Haskell function field
Pattern matching syntax is nearly identical
Pattern 2: Result/Either Error Handling
Elm:
type alias Error = String
parseAge : String -> Result Error Int
parseAge str =
case String.toInt str of
Just age ->
if age >= 0 then
Ok age
else
Err "Age must be non-negative"
Nothing ->
Err "Not a valid number"
validateUser : String -> String -> Result Error User
validateUser ageStr emailStr =
Result.andThen
(\age -> Result.map (User age) (validateEmail emailStr))
(parseAge ageStr)
Haskell:
import Text.Read (readMaybe)
type Error = String
parseAge :: String -> Either Error Int
parseAge str =
case readMaybe str of
Just age ->
if age >= 0
then Right age
else Left "Age must be non-negative"
Nothing ->
Left "Not a valid number"
validateUser :: String -> String -> Either Error User
validateUser ageStr emailStr = do
age <- parseAge ageStr
email <- validateEmail emailStr
return $ User age email
-- Or with Applicative
validateUser' :: String -> String -> Either Error User
validateUser' ageStr emailStr =
User <$> parseAge ageStr <*> validateEmail emailStr
Why this translation:
Result err ok maps to Either err ok (note: Left = error, Right = success)
Haskell's do-notation is more concise for chaining
Both support applicative style for independent validations
Pattern 3: List Operations
Elm:
processNumbers : List Int -> Int
processNumbers numbers =
numbers
|> List.filter (\x -> x > 0)
|> List.map (\x -> x * 2)
|> List.foldl (+) 0
-- List comprehension (rare in Elm)
squares : List Int
squares =
List.map (\x -> x * x) (List.range 1 10)
Haskell:
processNumbers :: [Int] -> Int
processNumbers numbers =
foldl (+) 0 $
map (*2) $
filter (>0) numbers
-- Or with function composition
processNumbers' :: [Int] -> Int
processNumbers' = foldl (+) 0 . map (*2) . filter (>0)
-- Or with pipeline using & (from Data.Function)
import Data.Function ((&))
processNumbers'' :: [Int] -> Int
processNumbers'' numbers =
numbers
& filter (>0)
& map (*2)
& foldl (+) 0
-- List comprehension (idiomatic in Haskell)
squares :: [Int]
squares = [x * x | x <- [1..10]]
Function composition (.) is more idiomatic in Haskell
List comprehensions more common in Haskell than Elm
Pattern 4: Pattern Matching and Case Expressions
Elm:
describeList : List a -> String
describeList list =
case list of
[] ->
"empty"
[ x ] ->
"singleton"
x :: xs ->
"list with multiple elements"
-- Destructuring in function parameters
head : List a -> Maybe a
head list =
case list of
[] -> Nothing
x :: _ -> Just x
Haskell:
describeList :: [a] -> String
describeList list =
case list of
[] -> "empty"
[x] -> "singleton"
(x:xs) -> "list with multiple elements"
-- Pattern matching in function definition (more idiomatic)
describeList' :: [a] -> String
describeList' [] = "empty"
describeList' [x] = "singleton"
describeList' (x:xs) = "list with multiple elements"
-- Direct pattern matching for head
head' :: [a] -> Maybe a
head' [] = Nothing
head' (x:_) = Just x
Why this translation:
Pattern matching is nearly identical
Haskell allows pattern matching in function definitions (more concise)
Syntax differences: x :: xs (Elm) vs (x:xs) (Haskell)
Both support guards and nested patterns
Pattern 5: Record Updates
Elm:
type alias User =
{ name : String
, age : Int
, email : String
}
updateAge : Int -> User -> User
updateAge newAge user =
{ user | age = newAge }
updateMultiple : User -> User
updateMultiple user =
{ user | age = user.age + 1, name = "Updated" }
Haskell:
data User = User
{ name :: String
, age :: Int
, email :: String
} deriving (Show, Eq)
updateAge :: Int -> User -> User
updateAge newAge user = user { age = newAge }
updateMultiple :: User -> User
updateMultiple user = user
{ age = age user + 1
, name = "Updated"
}
Why this translation:
Record update syntax is nearly identical
Haskell requires parentheses for field access: age user vs Elm's user.age
Both create new values (immutability)
Pattern 6: Custom Types and Constructors
Elm:
type Msg
= NoOp
| Increment
| Decrement
| SetValue Int
| SetName String
type RemoteData error value
= NotAsked
| Loading
| Success value
| Failure error
Haskell:
data Msg
= NoOp
| Increment
| Decrement
| SetValue Int
| SetName String
deriving (Show, Eq)
data RemoteData error value
= NotAsked
| Loading
| Success value
| Failure error
deriving (Show, Eq, Functor)
Why this translation:
Syntax is identical
Haskell allows deriving type classes (Show, Eq, Functor, etc.)
Elm auto-derives equality; Haskell requires explicit deriving Eq
The Elm Architecture → Haskell Patterns
TEA Structure
Elm's TEA:
-- MODEL
type alias Model = { count : Int }
init : () -> ( Model, Cmd Msg )
init _ = ( { count = 0 }, Cmd.none )
-- UPDATE
type Msg = Increment | Decrement
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Increment -> ( { model | count = model.count + 1 }, Cmd.none )
Decrement -> ( { model | count = model.count - 1 }, Cmd.none )
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model.count) ]
, button [ onClick Increment ] [ text "+" ]
]
Haskell Equivalent (no direct frontend):
For pure logic (testable):
-- MODEL
data Model = Model { count :: Int } deriving (Show, Eq)
init :: Model
init = Model { count = 0 }
-- UPDATE
data Msg = Increment | Decrement deriving (Show, Eq)
update :: Msg -> Model -> Model
update msg model =
case msg of
Increment -> model { count = count model + 1 }
Decrement -> model { count = count model - 1 }
-- No VIEW in backend context
-- For testing or CLI:
renderModel :: Model -> String
renderModel model = "Count: " ++ show (count model)
For interactive CLI:
import Control.Monad (forever)
import System.IO (hFlush, stdout)
-- REPL-style interaction
mainLoop :: Model -> IO ()
mainLoop model = forever $ do
putStrLn $ renderModel model
putStrLn "Commands: + (increment), - (decrement), q (quit)"
putStr "> "
hFlush stdout
input <- getLine
case input of
"+" -> mainLoop (update Increment model)
"-" -> mainLoop (update Decrement model)
"q" -> return ()
_ -> mainLoop model
main :: IO ()
main = mainLoop init
Why this adaptation:
Elm's Model/Update pattern translates to pure state machines in Haskell
Cmd becomes IO for side effects
Frontend view logic has no direct backend equivalent
For web: use Servant, Scotty, or Yesod with separate architecture
Cmd and Effects
Elm:
type Msg = GotUsers (Result Http.Error (List User))
getUsers : Cmd Msg
getUsers =
Http.get
{ url = "https://api.example.com/users"
, expect = Http.expectJson GotUsers (Json.Decode.list userDecoder)
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FetchUsers -> ( { model | loading = True }, getUsers )
GotUsers result -> ...
Haskell:
import Network.HTTP.Simple
import Data.Aeson (FromJSON, eitherDecode)
data Msg = GotUsers (Either String [User]) deriving (Show)
getUsers :: IO (Either String [User])
getUsers = do
response <- httpLBS "https://api.example.com/users"
return $ eitherDecode (getResponseBody response)
-- In a real app, you'd use async or STM for event handling
handleMsg :: Msg -> Model -> IO Model
handleMsg msg model =
case msg of
GotUsers (Right users) -> return $ model { users = users, loading = False }
GotUsers (Left err) -> return $ model { error = Just err, loading = False }
-- Async version
import Control.Concurrent.Async
fetchUsersAsync :: (Msg -> IO ()) -> IO ()
fetchUsersAsync dispatch = do
result <- getUsers
dispatch (GotUsers result)
Why this adaptation:
Elm's Cmd is a managed effect system; Haskell uses IO directly
Elm runtime handles effect execution; Haskell requires explicit async/threading
Consider using async, STM, or event libraries for complex state management
JSON Handling
JSON Decoders
Elm:
import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (required, optional)
type alias User =
{ name : String
, email : String
, age : Int
}
userDecoder : Decoder User
userDecoder =
Decode.succeed User
|> required "name" Decode.string
|> required "email" Decode.string
|> required "age" Decode.int
Haskell:
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson
import GHC.Generics
data User = User
{ name :: String
, email :: String
, age :: Int
} deriving (Generic, Show)
-- Automatic derivation (recommended)
instance FromJSON User
instance ToJSON User
-- Manual (for custom field names)
instance FromJSON User where
parseJSON = withObject "User" $ \v -> User
<$> v .: "name"
<*> v .: "email"
<*> v .: "age"
Why this translation:
Elm requires explicit decoders; Haskell can auto-derive via Generic
import Data.Aeson
-- Automatic (if using Generic)
encodeUser :: User -> Value
encodeUser = toJSON
-- Manual
instance ToJSON User where
toJSON (User n e a) = object
[ "name" .= n
, "email" .= e
, "age" .= a
]
Why this translation:
Both use object/record encoding
Haskell's .= operator is more concise than Elm's tuple syntax
Generics make simple cases trivial
Concurrency Patterns
Elm uses Cmd and Sub for managed effects. Haskell requires explicit concurrency handling.
Subscriptions → Event Streams
Elm:
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Time.every 1000 Tick
, Browser.Events.onResize WindowResized
]
Haskell (using async):
import Control.Concurrent (threadDelay, forkIO)
import Control.Concurrent.STM
import Control.Concurrent.Async
-- Event channel
type EventBus msg = TChan msg
-- Timer subscription
timerSub :: EventBus Msg -> IO ()
timerSub bus = forever $ do
threadDelay 1000000 -- 1 second
atomically $ writeTChan bus Tick
-- Main loop
mainWithSubs :: IO ()
mainWithSubs = do
eventBus <- newTChanIO
async $ timerSub eventBus
forever $ do
msg <- atomically $ readTChan eventBus
-- handle msg
return ()
Why this adaptation:
Elm's subscriptions are declarative; Haskell requires imperative setup
Use STM, async, or event libraries for similar patterns
Consider libraries like reactive-banana for FRP-style event handling
Common Pitfalls
1. Assuming Elm's Simplicity Limits Apply
Problem: Elm intentionally limits features (no type classes, no laziness control). Haskell has these features.
Solution:
Use type classes for polymorphism (Eq, Show, Functor, etc.)
Leverage lazy evaluation (but beware space leaks)
Use advanced type system features when beneficial (GADTs, type families)
2. Direct Translation of TEA
Problem: The Elm Architecture is frontend-specific. Direct translation to Haskell backend makes no sense.
Solution:
Extract pure business logic (Model + Update) - this translates directly
Rethink effects: Cmd → IO, async, or STM
For web backends: use Servant, Scotty, or Yesod patterns
3. Forgetting Field Accessor Syntax
Problem: Elm's user.name vs Haskell's name user.
Elm:
userName = user.name
Haskell:
userName = name user -- Function application
Solution: Remember Haskell record fields are functions.
4. Result vs Either Argument Order
Problem:Result err ok vs Either err ok - same order, but different conventions.
Elm:
type Result error value = Err error | Ok value
Haskell:
data Either a b = Left a | Right b
-- By convention: Left = error, Right = success
Solution:
Elm Ok x → Haskell Right x
Elm Err e → Haskell Left e
5. Missing Text Import
Problem: Using String instead of Text in production code.
Solution:
import Data.Text (Text)
import qualified Data.Text as T
-- Use Text for all string data
data User = User { name :: Text } deriving (Show)
Tooling
Transpilers & Converters
Tool
Direction
Status
Notes
haskelm
Haskell → Elm
Experimental
Template Haskell based
haskell-to-elm
Haskell → Elm
Active
Type + JSON codec generation
elm-bridge
Haskell ↔ Elm
Active
Bidirectional type sync
elm-street
Haskell → Elm
Active
Aeson-compatible codegen
Note: Most tools focus on Haskell → Elm for full-stack apps. Manual conversion recommended for Elm → Haskell.
Type Synchronization Libraries
For full-stack apps (Haskell backend + Elm frontend), these maintain type safety:
-- haskell-to-elm example
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Language.Elm.Pretty (pretty)
import qualified Language.Haskell.To.Elm as Elm
data User = User { name :: Text, age :: Int }
deriving (Generic, Elm.HasElmType, Elm.HasElmEncoder Aeson.Value, Elm.HasElmDecoder Aeson.Value)
-- Generates Elm type + JSON encoders/decoders
Testing Strategy
-- HSpec for behavior
import Test.Hspec
spec :: Spec
spec = describe "update function" $ do
it "increments count" $ do
let model = Model { count = 0 }
let result = update Increment model
count result `shouldBe` 1
-- QuickCheck for properties
import Test.QuickCheck
prop_updateIdempotent :: Msg -> Model -> Property
prop_updateIdempotent msg model =
update msg (update msg model) === update msg model
Examples
Example 1: Simple - Type Definitions and Functions
Before (Elm):
type alias Point =
{ x : Float
, y : Float
}
distance : Point -> Point -> Float
distance p1 p2 =
let
dx = p1.x - p2.x
dy = p1.y - p2.y
in
sqrt (dx * dx + dy * dy)
midpoint : Point -> Point -> Point
midpoint p1 p2 =
{ x = (p1.x + p2.x) / 2
, y = (p1.y + p2.y) / 2
}
After (Haskell):
data Point = Point
{ x :: Double
, y :: Double
} deriving (Show, Eq)
distance :: Point -> Point -> Double
distance p1 p2 =
let dx = x p1 - x p2
dy = y p1 - y p2
in sqrt (dx * dx + dy * dy)
midpoint :: Point -> Point -> Point
midpoint p1 p2 = Point
{ x = (x p1 + x p2) / 2
, y = (y p1 + y p2) / 2
}