スキル一覧に戻る
aRustyDev

convert-elm-roc

by aRustyDev

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

1🍴 1📅 2026年1月16日
GitHubで見るManusで実行

SKILL.md


name: convert-elm-roc description: Convert Elm code to idiomatic Roc. Use when migrating Elm frontend code to Roc applications, translating browser-based Elm to platform-agnostic Roc, or refactoring Elm web applications to Roc CLI/native tools. Extends meta-convert-dev with Elm-to-Roc specific patterns.

Convert Elm to Roc

Convert Elm code to idiomatic Roc. This skill extends meta-convert-dev with Elm-to-Roc specific type mappings, idiom translations, and architectural patterns for moving from browser-based Elm applications to platform-agnostic Roc code.

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: Elm types → Roc types
  • Idiom translations: Elm patterns → idiomatic Roc
  • Architecture patterns: The Elm Architecture (TEA) → Platform model
  • Effect system: Cmd/Sub → Task
  • Error handling: Result types (REVERSED parameter order!)
  • Platform shift: Frontend-specific → General-purpose

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Elm language fundamentals - see lang-elm-dev
  • Roc language fundamentals - see lang-roc-dev
  • Reverse conversion (Roc → Elm) - see convert-roc-elm
  • Browser-specific Elm code - Roc doesn't have DOM access

Quick Reference

ElmRocNotes
StringStrDirect mapping
IntI64 or U64Choose signed/unsigned based on domain
FloatF64Direct mapping
BoolBoolSame with capitalization
List aList aSame syntax and operations
{ field : Type }{ field : Type }Records are nearly identical
type Custom = Tag1 | Tag2[Tag1, Tag2]Custom types → Tag unions
Result err okResult ok errREVERSED parameter order!
Maybe a[Some a, None]Optional values
Cmd Msg or Task err aTask ok errEffect systems differ
case x ofwhen x isPattern matching syntax
Task.perform! suffix operatorExplicit handling → Bang operator

🚨 CRITICAL GOTCHA: Result Type Parameter Order

This is the most important thing to remember when converting Elm to Roc:

-- Elm: Result error ok
divide : Int -> Int -> Result String Int
# Roc: Result ok err (REVERSED!)
divide : I64, I64 -> Result I64 Str

Why This Matters

The parameter order is completely reversed between Elm and Roc:

  • Elm: Result error ok - Error type first, success type second
  • Roc: Result ok err - Success type first, error type second

This affects:

  • Type signatures
  • Type annotations
  • Generic type parameters
  • Error handling patterns

Always Remember

When you see Elm's Result String User, it becomes Roc's Result User Str.

Wrong: Result Str User (copying Elm order) ✓ Correct: Result User Str (reversed order)


Architectural Paradigm Shift

From The Elm Architecture to Platform Model

AspectElm TEARoc Platform Model
TargetBrowser frontend onlyAny platform (CLI, web, native)
EffectsRuntime-managed Cmd/SubPlatform-provided Task
Entry pointmain : Program () Model Msgmain : Task {} []
StateExplicit ModelImplicit in Task chain
Updatesupdate : Msg → Model → (Model, Cmd Msg)Task composition
I/OBrowser.* modules onlyPlatform exposes (File, Http, etc.)

Elm TEA Application

module Main exposing (main)

import Browser
import Html exposing (Html, div, input, text)
import Html.Events exposing (onInput)
import Html.Attributes exposing (placeholder, value)

-- MODEL
type alias Model =
    { name : String
    , greeting : String
    }

init : () -> ( Model, Cmd Msg )
init _ =
    ( { name = "", greeting = "Hello, World!" }, Cmd.none )

-- UPDATE
type Msg
    = NameChanged String

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NameChanged newName ->
            ( { model
                | name = newName
                , greeting = "Hello, " ++ newName ++ "!"
              }
            , Cmd.none
            )

-- VIEW
view : Model -> Html Msg
view model =
    div []
        [ div [] [ text model.greeting ]
        , input
            [ placeholder "Enter your name"
            , value model.name
            , onInput NameChanged
            ]
            []
        ]

-- MAIN
main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }

Roc Platform Equivalent

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br"
}

import pf.Stdout
import pf.Stdin
import pf.Task exposing [Task]

main : Task {} []
main =
    Stdout.line! "Hello, World!"
    Stdout.line! "Enter your name:"
    name = Stdin.line!
    Stdout.line! "Hello, \(name)!"

Key shift: Elm's declarative Model-Update-View loop becomes Roc's imperative Task chain.

Note: The Roc version is CLI-based because Roc doesn't target the browser. For equivalent browser functionality, you'd need a Roc web platform (still in development).


Type System Mapping

Primitive Types

ElmRocNotes
True / FalseBool.true / Bool.falseCapitalization differs
4242Integer literals
3.143.14Float literals
"text""text"String literals
IntI64 or U64Elm has arbitrary precision, Roc has sized types
FloatF64 or F32Elm has single Float, Roc has sized types
StringStrDirect mapping
CharU32Roc treats chars as Unicode scalar values

Collection Types

ElmRocNotes
List aList aIdentical syntax and semantics
Dict comparable vDict k vRoc requires k to implement Hash & Eq
Set comparableSet aRoc requires a to implement Hash & Eq
( a, b )(a, b)Tuples (Roc supports arbitrary tuple sizes)
Array aList aElm's Array → Roc's List (Roc optimizes internally)

Record Types

ElmRocNotes
{ name : String, age : Int }{ name : Str, age : U32 }Nearly identical, just type name differences
{ user | age = 31 }{ user & age: 31 }Record update syntax differs (| vs &, = vs :)
{ name, age } = user{ name, age } = userDestructuring identical
user.nameuser.nameField access identical

Custom Types to Tag Unions

Elm:

-- Named custom type (nominal)
type Color
    = Red
    | Green
    | Blue
    | Custom Int Int Int

type alias RGB =
    { r : Int, g : Int, b : Int }

Roc:

# Structural tag union
Color : [Red, Green, Blue, Custom(U8, U8, U8)]

# Record type alias
RGB : { r : U8, g : U8, b : U8 }

Key differences:

  • Elm requires explicit type declaration
  • Roc uses structural types (no declaration needed)
  • Elm uses type constructors with |
  • Roc uses tag union syntax with []

Optional Values

Elm:

-- Built-in Maybe type
email : Maybe String
email = Just "alice@example.com"

-- Pattern match
emailText : String
emailText =
    case email of
        Just addr ->
            addr
        Nothing ->
            "no email"

-- Helper functions
emailOrDefault : String
emailOrDefault =
    Maybe.withDefault "no email" email

Roc:

# Inline tag union (no built-in Maybe)
email : [Some Str, None]
email = Some("alice@example.com")

# Pattern match
emailText : Str
emailText =
    when email is
        Some(addr) -> addr
        None -> "no email"

# Manual helper or use Result

Translation:

  • Maybe a[Some a, None]
  • Just valueSome(value)
  • NothingNone

Result Type (Parameter Order Reversed!)

Elm:

-- Result error ok
divide : Int -> Int -> Result String Int
divide a b =
    if b == 0 then
        Err "Division by zero"
    else
        Ok (a // b)

Roc:

# Result ok err (REVERSED!)
divide : I64, I64 -> Result I64 Str
divide = \a, b ->
    if b == 0 then
        Err("Division by zero")
    else
        Ok(a // b)

CRITICAL:

  • Elm's Result error ok has error first
  • Roc's Result ok err has success first
  • Always reverse the parameter order when converting

Idiom Translation

1. Pattern Matching: case → when

Elm:

classify : Int -> String
classify n =
    case n of
        0 ->
            "zero"

        x ->
            if x < 0 then
                "negative"
            else
                "positive"

Roc:

classify : I64 -> Str
classify = \n ->
    when n is
        0 -> "zero"
        x if x < 0 -> "negative"
        _ -> "positive"

Why this translation:

  • Elm's case becomes Roc's when
  • Elm uses if expressions in branches, Roc has guard clauses (if after pattern)
  • Roc allows inline guards which are more concise

2. List Processing

Elm:

doubled : List Int
doubled =
    List.map (\x -> x * 2) [ 1, 2, 3, 4, 5 ]

-- Pipeline style
result : Int
result =
    [ 1, 2, 3, 4, 5 ]
        |> List.map (\x -> x * 2)
        |> List.filter (\x -> x > 5)
        |> List.foldl (+) 0

Roc:

doubled : List I64
doubled = List.map([1, 2, 3, 4, 5], \x -> x * 2)

# Pipeline style (same!)
result : I64
result = [1, 2, 3, 4, 5]
    |> List.map(\x -> x * 2)
    |> List.keepIf(\x -> x > 5)
    |> List.walk(0, Num.add)

Why this translation:

  • List.filterList.keepIf (different name, same semantics)
  • List.foldl / List.foldrList.walk (different name)
  • Pipeline operator |> is identical
  • Roc supports both List.map(list, fn) and List.map list fn syntax

3. Record Updates

Elm:

user =
    { name = "Alice", age = 30 }

olderUser =
    { user | age = 31 }

-- Multiple fields
updatedUser =
    { user
        | age = 31
        , name = "Alice Smith"
    }

Roc:

user = { name: "Alice", age: 30 }
olderUser = { user & age: 31 }

# Multiple fields
updatedUser = { user &
    age: 31,
    name: "Alice Smith"
}

Why this translation:

  • Elm uses | for updates, Roc uses &
  • Elm uses = for field assignment, Roc uses :
  • Syntax is almost identical otherwise

4. Cmd/Task → Task

Elm:

type Msg
    = GotData (Result Http.Error String)

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

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FetchData ->
            ( model, fetchData )

        GotData result ->
            case result of
                Ok data ->
                    ( { model | data = String.toUpper data }
                    , Cmd.none
                    )

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

Roc:

main : Task {} []
main =
    data = Http.get!("https://api.example.com/data")
    processed = Str.toUpper(data)
    Task.ok({})

Why this translation:

  • Elm's Cmd with Msg handling becomes Roc's direct Task chaining
  • Elm's Task.perform becomes Roc's ! operator
  • Elm's event-driven model becomes Roc's sequential execution
  • No Model or Msg types needed in Roc for simple cases

5. Error Propagation

Elm:

calculate : Int -> Int -> Int -> Result String Int
calculate a b c =
    divide a b
        |> Result.andThen (\x -> divide x c)

-- Or with explicit pattern matching
calculateExplicit : Int -> Int -> Int -> Result String Int
calculateExplicit a b c =
    case divide a b of
        Err e ->
            Err e
        Ok x ->
            case divide x c of
                Err e ->
                    Err e
                Ok y ->
                    Ok y

Roc:

# Roc: Result ok err (reversed params!)
calculate : I64, I64, I64 -> Result I64 Str
calculate = \a, b, c ->
    x = divide!(a, b)  # Returns early on Err
    y = divide!(x, c)  # Returns early on Err
    Ok(y)

Why this translation:

  • Elm's Result.andThen becomes Roc's ! operator
  • Roc's ! provides automatic early return on error
  • Much more concise than Elm's explicit chaining
  • Remember to reverse Result type parameters!

6. Opaque Types

Elm:

-- Elm uses module visibility for opacity
module Age exposing (Age, create, toInt)

type Age
    = Age Int

create : Int -> Maybe Age
create n =
    if n >= 0 && n < 150 then
        Just (Age n)
    else
        Nothing

toInt : Age -> Int
toInt (Age n) =
    n

-- Constructor Age is NOT exposed, only create function

Roc:

interface Age
    exposes [Age, create, toU32]
    imports []

# Opaque type
Age := U32

create : U32 -> Result Age [InvalidAge]
create = \n ->
    if n >= 0 && n < 150 then
        Ok(@Age(n))
    else
        Err(InvalidAge)

toU32 : Age -> U32
toU32 = \@Age(n) -> n

Why this translation:

  • Elm uses pattern matching for unwrapping, Roc uses @ syntax
  • Both achieve opacity through module exports
  • Roc's @Age(n) wrapping is more explicit than Elm's Age n
  • Roc uses Result for validation, Elm uses Maybe (different conventions)

Paradigm Translation: TEA → Platform Model

Mental Model Shift

Elm ConceptRoc ApproachKey Insight
Model-Update-View loopTask chain (sequential)Declarative → Imperative
Browser provides eventsPlatform provides I/OBrowser → CLI/Native
main returns Programmain returns TaskPure → Effect
Cmd issued, Msg receivedTasks compose with !Indirect → Direct

Effect System Comparison

Elm ModelRoc ModelConceptual Translation
Runtime handles Cmd/SubPlatform handles TasksBoth managed by runtime
Asynchronous with MsgSequential with !Event-driven → Chain
Cmd.none / new CmdTask.ok/Task.errSide effect → Return value

Example: HTTP Fetch

Elm (TEA):

type alias Model =
    { users : RemoteData Http.Error (List User)
    }

type Msg
    = FetchUsers
    | GotUsers (Result Http.Error (List User))

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FetchUsers ->
            ( { model | users = Loading }
            , Http.get
                { url = "/api/users"
                , expect = Http.expectJson GotUsers usersDecoder
                }
            )

        GotUsers result ->
            case result of
                Ok users ->
                    ( { model | users = Success users }, Cmd.none )

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

Roc (Platform):

main : Task {} []
main =
    users = Http.get!("/api/users")
    decoded = Decode.fromBytes!(users, usersDecoder)
    Stdout.line!("Fetched \(List.len(decoded) |> Num.toStr) users")

Key differences:

  • Elm models loading states explicitly
  • Roc handles success/error sequentially
  • Elm's async becomes Roc's sequential (platform handles concurrency)
  • No Model or Msg types needed in Roc

Error Handling

Elm Result → Roc Result

Key Difference: Parameter order is reversed!

-- Elm: Result err ok
parseAge : String -> Result String Int
parseAge str =
    case String.toInt str of
        Just age ->
            if age >= 0 then
                Ok age
            else
                Err "Negative age"

        Nothing ->
            Err "Not a number"
# Roc: Result ok err (REVERSED!)
parseAge : Str -> Result U32 Str
parseAge = \str ->
    when Str.toU32(str) is
        Ok(age) if age >= 0 -> Ok(age)
        Ok(_) -> Err("Negative age")
        Err(_) -> Err("Not a number")

Error Type Modeling

Elm uses named custom types:

type FetchError
    = NetworkError Http.Error
    | NotFound
    | Unauthorized

fetchUser : Int -> Task FetchError User

Roc uses inline tag unions:

fetchUser : U64 -> Task User [NetworkError, NotFound, Unauthorized]

Translation:

  • Elm's named error types → Roc's inline tag unions
  • Same expressiveness, less ceremony
  • Roc is structural, Elm is nominal

Effect System Translation

Cmd in Elm vs Task in Roc

Elm Cmd (event-driven):

type Msg
    = GotData (Result Http.Error String)

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

-- Must handle result in update function
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotData result ->
            -- Handle result here
            ...

Roc Task (sequential):

fetchData : Task Str []
fetchData =
    Http.get!("https://api.example.com/data")

Why this translation:

  • Elm's Cmd is fire-and-forget, result comes via Msg
  • Roc's Task chains sequentially with !
  • Elm separates effect from handling, Roc combines them

Sub in Elm vs Task in Roc

Elm Subscriptions:

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ Time.every 1000 Tick
        , Browser.Events.onResize WindowResized
        ]

Roc approach: Roc doesn't have built-in subscriptions. Platforms may provide equivalent mechanisms through Task-based polling or event streams, but this is platform-specific.

For periodic tasks, you'd typically use platform-specific APIs or structure your main Task to loop.


Module System Translation

Elm Modules → Roc Interfaces

Elm:

module User exposing (User, create, getName, getAge)

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

create : String -> Int -> User
create name age =
    { name = name, age = age }

getName : User -> String
getName user =
    user.name

getAge : User -> Int
getAge user =
    user.age

Roc:

interface User
    exposes [User, create, getName, getAge]
    imports []

User : {
    name : Str,
    age : U32,
}

create : Str, U32 -> User
create = \name, age ->
    { name, age }

getName : User -> Str
getName = \user -> user.name

getAge : User -> U32
getAge = \user -> user.age

Translation:

  • moduleinterface
  • exposingexposes
  • type alias → type annotation
  • Same visibility model (only exposed items are public)

Import Patterns

Elm:

import Dict
import Dict exposing (Dict)
import List exposing (map, filter)
import Maybe exposing (Maybe(..))
import Html as H
import Html.Events as Events

Roc:

import Dict
import Dict exposing [Dict]
import List exposing [map, keepIf]
import pf.Stdout
import pf.Task exposing [Task]

# Note: Roc doesn't have import aliasing yet
# Must use full qualified names

Translation:

  • exposingexposing
  • Parentheses () → Brackets []
  • Elm's import as → Not yet available in Roc

Common Pitfalls

  1. Result parameter order reversal (MOST CRITICAL)

    • Elm: Result err ok
    • Roc: Result ok err
    • Always reverse parameters when converting Result types
    • Double-check every Result type signature!
  2. Record update syntax

    • Elm: { record | field = value }
    • Roc: { record & field: value }
    • Don't mix up |/& and =/:
  3. Case vs When syntax

    • Elm: case x of
    • Roc: when x is
    • Remember is not of
  4. Platform target mismatch

    • Elm targets browser only (DOM, HTML, CSS)
    • Roc is platform-agnostic (CLI, native, potentially web)
    • Browser-specific Elm code needs redesign
  5. Bang operator vs explicit Task

    • Elm: No ! operator, use Task.perform or Cmd
    • Roc: value = task! for sequential execution
    • Much more concise in Roc
  6. Capitalization

    • Elm: True, False
    • Roc: Bool.true, Bool.false
    • Watch for True/False differences
  7. Function types

    • Elm: a -> b -> c (curried)
    • Roc: a, b -> c (comma-separated params)
    • Roc allows both, but commas are clearer
  8. Maybe vs tag union

    • Elm: Built-in Maybe a with Just/Nothing
    • Roc: Use [Some a, None] (no built-in Maybe)
    • Must define tag union explicitly
  9. List function names

    • Elm: List.filter, List.foldl, List.foldr
    • Roc: List.keepIf, List.walk
    • Same concepts, different names
  10. String interpolation

    • Elm: "Hello, " ++ name ++ "!"
    • Roc: "Hello, \(name)!"
    • Roc has built-in string interpolation

Tooling

ToolElmRocNotes
Formatterelm-formatroc formatBoth enforce standard style
REPLelm replroc replBoth support interactive testing
Testelm-testroc testDifferent syntax (case vs expect)
Buildelm makeroc buildElm → JavaScript, Roc → native
Package managerelm installPlatform URLsRoc uses URL-based dependencies
Linterelm-reviewN/AElm has rich linting, Roc doesn't yet

Examples

Example 1: Simple - Type and Function Translation

Before (Elm):

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

greet : User -> String
greet user =
    "Hello, " ++ user.name ++ "! You are " ++ String.fromInt user.age ++ " years old."

-- Test
import Test exposing (test)
import Expect

suite =
    test "greet formats message correctly" <|
        \_ ->
            greet { name = "Alice", age = 30 }
                |> Expect.equal "Hello, Alice! You are 30 years old."

After (Roc):

User : { name : Str, age : U32 }

greet : User -> Str
greet = \user ->
    "Hello, \(user.name)! You are \(Num.toStr(user.age)) years old."

expect greet({ name: "Alice", age: 30 }) == "Hello, Alice! You are 30 years old."

Key changes:

  • StringStr, IntU32
  • String concatenation ++ → interpolation \(...)
  • String.fromIntNum.toStr
  • Elm's separate test file → inline expect
  • type alias → type annotation

Example 2: Medium - Custom Types and Pattern Matching

Before (Elm):

type Color
    = Red
    | Green
    | Blue
    | Custom Int Int Int

toHex : Color -> String
toHex color =
    case color of
        Red ->
            "#FF0000"

        Green ->
            "#00FF00"

        Blue ->
            "#0000FF"

        Custom r g b ->
            "#" ++ toHexByte r ++ toHexByte g ++ toHexByte b

toHexByte : Int -> String
toHexByte n =
    -- Implementation using Hex library
    String.fromInt n  -- Simplified

After (Roc):

Color : [Red, Green, Blue, Custom(U8, U8, U8)]

toHex : Color -> Str
toHex = \color ->
    when color is
        Red -> "#FF0000"
        Green -> "#00FF00"
        Blue -> "#0000FF"
        Custom(r, g, b) ->
            "#\(toHexByte(r))\(toHexByte(g))\(toHexByte(b))"

toHexByte : U8 -> Str
toHexByte = \n ->
    # Implementation
    Num.toStr(n)  # Simplified

Key changes:

  • Named type declaration → Structural tag union
  • case x ofwhen x is
  • IntU8 (Roc has sized integers)
  • String concatenation → interpolation
  • Constructor Custom r g bCustom(r, g, b)

Example 3: Complex - TEA to Platform Model

Before (Elm):

module Main exposing (main)

import Browser
import Html exposing (Html, div, text, button)
import Html.Events exposing (onClick)
import Http
import Json.Decode as Decode exposing (Decoder)

-- MODEL

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

type RemoteData e a
    = NotAsked
    | Loading
    | Success a
    | Failure e

type alias Model =
    { user : RemoteData Http.Error User
    }

init : () -> ( Model, Cmd Msg )
init _ =
    ( { user = NotAsked }, Cmd.none )

-- UPDATE

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

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FetchUser ->
            ( { model | user = Loading }
            , fetchUser 1
            )

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

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

-- HTTP

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

userDecoder : Decoder User
userDecoder =
    Decode.map3 User
        (Decode.field "id" Decode.int)
        (Decode.field "name" Decode.string)
        (Decode.field "email" Decode.string)

-- VIEW

view : Model -> Html Msg
view model =
    div []
        [ case model.user of
            NotAsked ->
                button [ onClick FetchUser ] [ text "Fetch User" ]

            Loading ->
                text "Loading..."

            Success user ->
                div []
                    [ text ("User: " ++ user.name ++ " (" ++ user.email ++ ")")
                    ]

            Failure error ->
                text ("Error: " ++ httpErrorToString error)
        ]

httpErrorToString : Http.Error -> String
httpErrorToString error =
    case error of
        Http.BadUrl url ->
            "Bad URL: " ++ url

        Http.Timeout ->
            "Timeout"

        Http.NetworkError ->
            "Network error"

        Http.BadStatus status ->
            "Bad status: " ++ String.fromInt status

        Http.BadBody body ->
            "Bad body: " ++ body

-- MAIN

main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }

After (Roc):

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br"
}

import pf.Http
import pf.Stdout
import pf.Task exposing [Task]
import json.Decode

# Note: Result type parameters REVERSED!
# Elm: Result Http.Error User
# Roc: Result User [HttpErr]

User : { id : U64, name : Str, email : Str }

fetchUser : U64 -> Task User [HttpErr, DecodeErr]
fetchUser = \userId ->
    url = "https://api.example.com/users/\(Num.toStr(userId))"
    response = Http.get!(url)

    when Decode.fromBytes(response.body, userDecoder) is
        Ok(user) -> Task.ok(user)
        Err(err) -> Task.err(DecodeErr)

userDecoder : Decode.Decoder User
userDecoder =
    Decode.record(\field ->
        {
            id: field.required("id", Decode.u64),
            name: field.required("name", Decode.str),
            email: field.required("email", Decode.str),
        }
    )

main : Task {} []
main =
    when fetchUser(1) is
        Ok(user) ->
            Stdout.line!("User: \(user.name) (\(user.email))")

        Err(HttpErr) ->
            Stdout.line!("HTTP error occurred")

        Err(DecodeErr) ->
            Stdout.line!("Failed to decode user")

Key changes:

  • Elm's TEA (Model-Update-View) → Roc's Task chain
  • Elm's Cmd Msg handling → Roc's ! operator
  • Elm's HTML view → Roc's CLI output
  • Elm's loading states → Roc's direct execution
  • Elm's Result Http.Error User → Roc's Result User [HttpErr, DecodeErr] (reversed params!)
  • No Model, Msg, or update function needed
  • Direct error handling with pattern matching

Testing Translation

Elm's elm-test → Roc's expect

Elm:

-- tests/UserTests.elm
module UserTests exposing (suite)

import Test exposing (Test, describe, test)
import Expect
import User

suite : Test
suite =
    describe "User module"
        [ describe "greet"
            [ test "formats greeting correctly" <|
                \_ ->
                    User.greet { name = "Alice", age = 30 }
                        |> Expect.equal "Hello, Alice! You are 30 years old."

            , test "handles young age" <|
                \_ ->
                    User.greet { name = "Bob", age = 5 }
                        |> Expect.equal "Hello, Bob! You are 5 years old."
            ]
        ]

Roc:

# User.roc
interface User
    exposes [User, greet]
    imports []

User : { name : Str, age : U32 }

greet : User -> Str
greet = \user ->
    "Hello, \(user.name)! You are \(Num.toStr(user.age)) years old."

# Inline tests
expect greet({ name: "Alice", age: 30 }) == "Hello, Alice! You are 30 years old."
expect greet({ name: "Bob", age: 5 }) == "Hello, Bob! You are 5 years old."

Translation:

  • Elm's separate test files → Roc's inline expect
  • Elm's describe and test → Roc's flat expect statements
  • Elm's Expect.equal → Roc's == operator
  • Run with elm-test → Run with roc test

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational patterns with cross-language examples
  • convert-roc-elm - Reverse conversion (Roc → Elm)
  • convert-elm-haskell - Similar functional language conversion patterns
  • lang-elm-dev - Elm development patterns
  • lang-roc-dev - Roc development patterns

Cross-cutting pattern skills:

  • patterns-concurrency-dev - Cmd/Sub vs Task comparison
  • patterns-serialization-dev - JSON encoding/decoding across languages
  • patterns-metaprogramming-dev - Why both languages avoid metaprogramming

スコア

総合スコア

55/100

リポジトリの品質指標に基づく評価

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

レビュー

💬

レビュー機能は近日公開予定です