Skip to content
convert-fsharp-roc logo

Convert F# to Roc

convert-fsharp-roc

Convert F# code to idiomatic Roc. Use when migrating F# projects to Roc, translating F# patterns to idiomatic Roc, or refactoring F# codebases. Extends meta-convert-dev with F#-to-Roc specific patterns.

SKILL.md

Full skill instructions

Convert F# to Roc

Convert F# code to idiomatic Roc. This skill extends meta-convert-dev with F#-to-Roc specific type mappings, idiom translations, and architectural guidance.

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: F# types → Roc types
  • Idiom translations: F# patterns → idiomatic Roc
  • Error handling: F# Result/​Option → Roc Result/​tag unions
  • Platform shift: .NET runtime → Roc platform model
  • Paradigm alignment: Both functional-first, but different architectures

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • F# language fundamentals - see lang-fsharp-dev
  • Roc language fundamentals - see lang-roc-dev
  • Reverse conversion (Roc → F#) - see convert-roc-fsharp

Quick Reference

F#RocNotes
stringStrImmutable strings
intI64Default signed integer
floatF6464-bit floating point
boolBoolBoolean values
'a listList aImmutable lists
'a arrayList aArrays become lists
Map<'k,'v>Dict k vImmutable dictionaries
Set<'a>Set aImmutable sets
Option<'a>[Some a, None]Optional values
Result<'a,'e>Result a eError handling
`{...}` anonymous record
type X = ... discriminated union[...] tag unionSum types
Async<'a>Task a errAsync/​effects via platform
unit{}Empty record (not quite ())

When Converting Code

  1. Analyze source thoroughly before writing target
  2. Map types first - create type equivalence table
  3. Preserve semantics over syntax similarity
  4. Adopt Roc idioms - understand platform/​application split
  5. Handle edge cases - null handling, error paths, effects
  6. Test equivalence - same inputs → same outputs

Paradigm Translation

Mental Model Shift: .NET Runtime → Platform Model

Both F# and Roc are functional-first languages, but they differ fundamentally in how they handle effects:

F# ConceptRoc ApproachKey Insight
.NET runtime with GCPlatform provides runtimeRuntime is external to application
Async<'a> workflowsTask ok err via platformEffects delegated to platform
Direct I/​O (Console, File)Platform-provided I/​OApplication remains pure
Mutable state allowedImmutable by defaultNo mutable keyword
Exception handlingResult type onlyNo runtime exceptions
Type providersCode generation externalNo compile-time metaprogramming

Architecture Mental Model

F# (.NET)                          Roc (Platform Model)
┌─────────────────────┐           ┌─────────────────────┐
│   Your F# Code      │           │   Your Roc Code     │
│  (can do I/​O)       │           │   (pure only)       │
│  ↓                  │           │   ↓                 │
│  .NET BCL           │           │   Platform API      │
│  ↓                  │           │   ↓                 │
│  CLR Runtime        │           │   Platform Host     │
└─────────────────────┘           └─────────────────────┘
     Everything in                    Clear separation
     same runtime                     between pure & effects

Key shift: In F#, you can call Console.WriteLine anywhere. In Roc, all I/​O goes through the platform's Task type.


Type System Mapping

Primitive Types

F#RocNotes
stringStrBoth immutable UTF-8
intI64F# int is 32-bit, Roc defaults to 64
int16, int32, int64I16, I32, I64Explicit sizes match
uint16, uint32, uint64U16, U32, U64Unsigned variants
byteU88-bit unsigned
sbyteI88-bit signed
float, doubleF32, F64F# float is F64
decimalNo direct equivalentUse external library or F64
boolBoolDirect mapping
charUse StrRoc has no char type
unit{}Empty record, not ()

Collection Types

F#RocNotes
'a listList aBoth immutable, structural sharing
'a arrayList aRoc lists handle array use cases
'a seqList aLazy sequences become lists
Map<'k,'v>Dict k vImmutable maps
Set<'a>Set aImmutable sets
('a * 'b) tuple(a, b)Tuples map directly
ResizeArray<'a>List aMutable becomes immutable

Composite Types

F#RocNotes
type X = { ... } record{ ... } recordStructural typing in both
`{...}` anonymous record
type X = A | B | C DU[A, B, C] tag unionDirect correspondence
type X = A of int single-case DU[A I64] or opaque typeFor newtype, use opaque
Option<'a>[Some a, None]Built-in DU vs tag union
Result<'ok,'err>Result ok errBuilt-in DU vs tag union
Choice<'a,'b>[A a, B b] tag unionNo built-in Choice

F# Specific Types → Roc

F# TypeRoc StrategyNotes
Async<'a>Task a errPlatform-provided
Task<'a> (.NET Task)Task a errPlatform-provided
Lazy<'a>Thunks ({} -> a)No built-in lazy
ref<'a>Not neededNo mutable references
'a -> 'b functiona -> bFunctions map directly
Type providersExternal codegenNo compile-time metaprogramming
Units of measureCustom validationNo built-in units

Idiom Translation

Pattern 1: Option Handling

F#:

let findUser id =
    users |> List.tryFind (fun u -> u.Id = id)

let userName =
    findUser 1
    |> Option.map (fun u -> u.Name)
    |> Option.defaultValue "Unknown"

Roc:

findUser : I64 -> [Some User, None]
findUser = \id ->
    List.findFirst(users, \u -> u.id == id)
    |> Result.toOption  # Convert Result to Option-like tag

userName =
    when findUser(1) is
        Some(u) -> u.name
        None -> "Unknown"

Why this translation:

  • F# has built-in Option<'a> type; Roc uses tag unions [Some a, None]
  • F# has Option.map; Roc uses pattern matching with when
  • Both are structural sum types under the hood

Pattern 2: Result for Error Handling

F#:

let divide x y =
    if y = 0 then
        Error "Division by zero"
    else
        Ok (x / y)

let calculate a b c =
    result {
        let! x = divide a b
        let! y = divide x c
        return y
    }

Roc:

divide : I64, I64 -> Result I64 [DivByZero]
divide = \x, y ->
    if y == 0 then
        Err(DivByZero)
    else
        Ok(x // y)

calculate : I64, I64, I64 -> Result I64 [DivByZero]
calculate = \a, b, c ->
    x = divide!(a, b)  # Try operator for error propagation
    y = divide!(x, c)
    Ok(y)

Why this translation:

  • F# has computation expressions (result { ... }); Roc uses try operator (!)
  • F# Error "msg" uses strings; Roc Err(DivByZero) uses typed tags
  • Both propagate errors up the call stack

Pattern 3: List Operations

F#:

let result =
    items
    |> List.filter (fun x -> x.Active)
    |> List.map (fun x -> x.Value)
    |> List.sum

Roc:

result =
    items
    |> List.keepIf(\x -> x.active)  # filter → keepIf
    |> List.map(\x -> x.value)
    |> List.walk(0, Num.add)  # sum via walk (fold)

Why this translation:

  • F# filter → Roc keepIf (more descriptive name)
  • F# sum → Roc walk(0, Num.add) (explicit fold)
  • Both use pipeline operator (|>) idiomatically

Pattern 4: Pattern Matching

F#:

type Color =
    | Red
    | Green
    | Blue
    | Custom of r: int * g: int * b: int

let describe color =
    match color with
    | Red -> "red"
    | Green -> "green"
    | Blue -> "blue"
    | Custom (r, g, b) -> $"rgb({r}, {g}, {b})"

Roc:

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

describe : Color -> Str
describe = \color ->
    when color is
        Red -> "red"
        Green -> "green"
        Blue -> "blue"
        Custom(r, g, b) -> "rgb(\(Num.toStr(r)), \(Num.toStr(g)), \(Num.toStr(b)))"

Why this translation:

  • F# discriminated unions → Roc tag unions (nearly identical)
  • F# match → Roc when (same exhaustiveness checking)
  • F# interpolation $"{x}" → Roc interpolation \(x) (different syntax)

Pattern 5: Record Updates

F#:

type Person = {
    FirstName: string
    LastName: string
    Age: int
}

let person = { FirstName = "Alice"; LastName = "Smith"; Age = 30 }
let olderPerson = { person with Age = 31 }

Roc:

Person : {
    firstName : Str,
    lastName : Str,
    age : U32,
}

person = { firstName: "Alice", lastName: "Smith", age: 30 }
olderPerson = { person & age: 31 }

Why this translation:

  • F# uses with keyword; Roc uses & operator
  • Both create new records (copy-on-write)
  • F# uses PascalCase by convention; Roc uses camelCase

Pattern 6: Pipeline Composition

F#:

let processUser =
    fetchUser
    >> validateUser
    >> saveUser

// Or with pipe
let result =
    userId
    |> fetchUser
    |> validateUser
    |> saveUser

Roc:

# Roc doesn't have >> composition operator
# Use pipeline instead
result =
    userId
    |> fetchUser
    |> validateUser
    |> saveUser

Why this translation:

  • F# has both >> (forward composition) and |> (pipeline)
  • Roc only has |> (pipeline) - prefer this style
  • Same left-to-right data flow

Error Handling

F# Exception Model → Roc Result Model

F# supports both exceptions and Result<'a,'e>. Roc only has Result.

F#:

// Style 1: Exceptions
let divide x y =
    if y = 0 then
        raise (DivideByZeroException())
    else
        x / y

try
    let result = divide 10 0
    printfn $"Result: {result}"
with
| :? DivideByZeroException -> printfn "Cannot divide by zero"

// Style 2: Result (preferred for F# interop)
let safeDivide x y =
    if y = 0 then
        Error "Division by zero"
    else
        Ok (x / y)

Roc:

# Only Result style - no exceptions
divide : I64, I64 -> Result I64 [DivByZero]
divide = \x, y ->
    if y == 0 then
        Err(DivByZero)
    else
        Ok(x // y)

# Handling
when divide(10, 0) is
    Ok(result) -> Stdout.line!("Result: \(Num.toStr(result))")
    Err(DivByZero) -> Stdout.line!("Cannot divide by zero")

Migration strategy:

  1. Convert all F# exceptions to Roc Result types
  2. Convert F# try/​with to Roc when ... is pattern matching
  3. Use ! (try operator) for error propagation instead of exception bubbling

Multiple Error Types

F#:

type ValidationError =
    | EmptyName
    | InvalidAge
    | InvalidEmail

let validatePerson name age email =
    if String.IsNullOrWhiteSpace(name) then
        Error EmptyName
    elif age < 0 || age > 120 then
        Error InvalidAge
    elif not (email.Contains("@")) then
        Error InvalidEmail
    else
        Ok { Name = name; Age = age; Email = email }

Roc:

ValidationError : [EmptyName, InvalidAge, InvalidEmail]

validatePerson : Str, I64, Str -> Result Person ValidationError
validatePerson = \name, age, email ->
    if Str.isEmpty(name) then
        Err(EmptyName)
    else if age < 0 || age > 120 then
        Err(InvalidAge)
    else if !(Str.contains(email, "@")) then
        Err(InvalidEmail)
    else
        Ok({ name, age, email })

Why this translation:

  • Both use discriminated unions/​tag unions for error types
  • Both use Result for success/​failure
  • Both have exhaustive pattern matching

Async and Effects

F# Async → Roc Task

This is a significant paradigm shift. F# Async runs on the .NET runtime; Roc Task is platform-provided.

F#:

let fetchData url = async {
    let! response = httpClient.GetStringAsync(url) |> Async.AwaitTask
    return response
}

let processMultiple urls = async {
    let! results =
        urls
        |> List.map fetchData
        |> Async.Parallel
    return Array.toList results
}

// Run the async
let result = processMultiple urls |> Async.RunSynchronously

Roc:

# Platform provides Task and Http
import pf.Http
import pf.Task exposing [Task]

fetchData : Str -> Task Str [HttpErr]
fetchData = \url ->
    Http.get!(url)  # Platform handles async

processMultiple : List Str -> Task (List Str) [HttpErr]
processMultiple = \urls ->
    # Platform may parallelize this
    urls
    |> List.map(fetchData)
    |> Task.sequence  # Platform-provided

# main is already a Task - no explicit run
main : Task {} []
main =
    results = processMultiple!(urls)
    Stdout.line!("Done")

Why this translation:

  • F# Async<'a> → Roc Task a err (platform-provided)
  • F# let! → Roc ! suffix (try operator)
  • F# Async.Parallel → Roc Task.sequence (platform decides parallelism)
  • F# needs Async.RunSynchronously; Roc main is already a Task

Pure vs Effectful Code

F#:

// Pure computation
let add x y = x + y

// Effectful computation (can do I/​O anywhere)
let greet name =
    printfn $"Hello, {name}!"
    name

Roc:

# Pure computation
add : I64, I64 -> I64
add = \x, y -> x + y

# Effectful computation (must return Task)
greet : Str -> Task Str []
greet = \name ->
    Stdout.line!("Hello, \(name)!")
    Task.ok(name)  # Return pure value in Task

Migration strategy:

  1. Identify all F# code that does I/​O
  2. Restructure to separate pure logic from effects
  3. Move effects to platform Task boundaries
  4. Keep business logic pure

Platform Architecture

.NET Application → Roc Application + Platform

F# (.NET Console App):

[<EntryPoint>]
let main argv =
    let input = Console.ReadLine()
    let processed = processInput input
    Console.WriteLine(processed)
    0  // Return exit code

Roc (Platform-based):

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

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

main : Task {} []
main =
    input = Stdin.line!
    processed = processInput(input)  # Pure function
    Stdout.line!(processed)

# Pure helper (no effects)
processInput : Str -> Str
processInput = \input ->
    Str.toUpper(input)

Key differences:

  • F# entry point is a function that returns int (exit code)
  • Roc entry point is a Task that the platform executes
  • F# can call I/​O anywhere; Roc separates pure from effectful code

Common Pitfalls

  1. Assuming F# mutability works in Roc

    • F# allows mutable keyword and ref cells
    • Roc has no mutable variables
    • Fix: Redesign with immutable data structures
  2. Trying to use F# exceptions

    • F# has raise, try/​with, exception types
    • Roc only has Result type
    • Fix: Convert all exceptions to Result with typed errors
  3. Expecting .NET BCL libraries

    • F# has access to entire .NET Base Class Library
    • Roc only has what the platform provides
    • Fix: Check platform docs for available APIs
  4. Using F# computation expressions freely

    • F# has async { }, result { }, seq { }, etc.
    • Roc only has pattern matching and ! operator
    • Fix: Use when ... is and ! for control flow
  5. Assuming type providers exist

    • F# type providers generate types at compile time
    • Roc has no metaprogramming
    • Fix: Use external code generation tools
  6. Forgetting platform/​application split

    • F# code is all in the same runtime
    • Roc strictly separates pure (app) from effects (platform)
    • Fix: Keep business logic pure, push effects to boundaries
  7. Using F# units of measure

    • F# has [<Measure>] attribute for type-safe calculations
    • Roc has no built-in units
    • Fix: Use opaque types with smart constructors for validation
  8. Expecting REPL-driven development

    • F# has F# Interactive (FSI) for REPL workflows
    • Roc supports roc repl but it's more limited
    • Fix: Use expect for inline tests instead

Module System

F# Modules/​Namespaces → Roc Interfaces

F#:

// UserModule.fs
namespace MyApp

module User =
    type User = {
        Id: int
        Name: string
        Email: string
    }

    let create name email = {
        Id = generateId()
        Name = name
        Email = email
    }

    let getName user = user.Name

Roc:

# User.roc
interface User
    exposes [User, create, getName]
    imports []

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

create : Str, Str -> User
create = \name, email -> {
    id: generateId(),
    name,
    email,
}

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

Migration notes:

  • F# namespaces → Not needed in Roc (file-based modules)
  • F# modules → Roc interfaces
  • F# exposes is explicit in Roc, implicit in F#

Build System

.NET Project → Roc Application

F# (.fsproj):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</​OutputType>
    <TargetFramework>net8.0</​TargetFramework>
  </​PropertyGroup>

  <ItemGroup>
    <Compile Include="Types.fs" />
    <Compile Include="Logic.fs" />
    <Compile Include="Program.fs" />
  </​ItemGroup>

  <ItemGroup>
    <PackageReference Include="FSharp.Data" Version="6.3.0" />
  </​ItemGroup>
</​Project>

Roc:

# main.roc - single file or multiple interfaces
app [main] {
    pf: platform "https://..."
}

import Types
import Logic

main : Task {} []
main =
    Logic.run

Build commands:

# F#
dotnet build
dotnet run

# Roc
roc build main.roc
roc run main.roc

Key differences:

  • F# needs .fsproj and explicit file ordering
  • Roc infers dependencies from imports
  • F# uses NuGet for packages; Roc uses platform URLs

Testing

F# Testing → Roc Expect

F# (Expecto):

module Tests

open Expecto

[<Tests>]
let tests =
    testList "Math tests" [
        testCase "addition" <| fun () ->
            Expect.equal (2 + 2) 4 "2 + 2 = 4"

        testCase "division by zero" <| fun () ->
            let result = divide 10 0
            Expect.equal result (Error "Division by zero") "should error"
    ]

[<EntryPoint>]
let main args =
    runTestsWithCLIArgs [] args tests

Roc:

# Inline tests with expect
add : I64, I64 -> I64
add = \x, y -> x + y

expect add(2, 2) == 4

divide : I64, I64 -> Result I64 [DivByZero]
divide = \x, y ->
    if y == 0 then
        Err(DivByZero)
    else
        Ok(x // y)

expect divide(10, 0) == Err(DivByZero)
expect divide(10, 2) == Ok(5)

Run tests:

# F#
dotnet test

# Roc
roc test main.roc

Migration strategy:

  • Convert Expecto/​xUnit/​NUnit tests to Roc expect statements
  • Place expects near the functions they test
  • Run with roc test

Limitations

Coverage Gaps

PillarF# SkillRoc SkillMitigation
Module✓✓Both well-documented
Error✓ (Result + exceptions)✓ (Result only)See Error Handling section
Concurrency~ (Async covered)✓See Async and Effects section
Metaprogramming~ (Type providers)✓ (minimalist)External code generation
Zero/​Default✓ (implicit)~ (via pattern matching)Use tag unions for nullable
Serialization✓~ (via abilities)See patterns-serialization-dev
Build✓~ (emerging)Roc build system is simpler
Testing✓✓Both covered adequately

Combined Score: 14/​16 (Good)

Known Limitations:

  1. Metaprogramming: F# type providers have no Roc equivalent; use external codegen
  2. Serialization: F# has rich JSON/​XML libraries; Roc relies on platform Encode/​Decode abilities
  3. Concurrency: F# Async is mature; Roc Task model is platform-dependent

External Resources Used

ResourceWhat It ProvidedReliability
F# for Fun and ProfitIdiom examplesHigh
Roc TutorialPlatform model guidanceHigh
lang-fsharp-devType system detailsHigh
lang-roc-devTask and platform patternsHigh

Tooling

ToolPurposeNotes
roc CLIBuild, run, test, formatEquivalent to dotnet CLI
Roc LSPEditor supportVS Code, vim, etc.
roc formatCode formattingLike fantomas for F#
roc testRun inline expectsLike dotnet test
External codegenType generationReplaces F# type providers

Examples

Example 1: Simple - Option Handling

Before (F#):

type User = { Id: int; Name: string; Email: string }

let users = [
    { Id = 1; Name = "Alice"; Email = "[email protected]" }
    { Id = 2; Name = "Bob"; Email = "[email protected]" }
]

let findUserById id =
    users |> List.tryFind (fun u -> u.Id = id)

let getUserName id =
    findUserById id
    |> Option.map (fun u -> u.Name)
    |> Option.defaultValue "Unknown"

After (Roc):

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

users = [
    { id: 1, name: "Alice", email: "[email protected]" },
    { id: 2, name: "Bob", email: "[email protected]" },
]

findUserById : I64 -> [Some User, None]
findUserById = \id ->
    when List.findFirst(users, \u -> u.id == id) is
        Ok(user) -> Some(user)
        Err(_) -> None

getUserName : I64 -> Str
getUserName = \id ->
    when findUserById(id) is
        Some(u) -> u.name
        None -> "Unknown"

Example 2: Medium - Result Error Handling

Before (F#):

type ValidationError =
    | InvalidName
    | InvalidAge

type Person = { Name: string; Age: int }

let validateName name =
    if String.IsNullOrWhiteSpace(name) then
        Error InvalidName
    else
        Ok name

let validateAge age =
    if age < 0 || age > 120 then
        Error InvalidAge
    else
        Ok age

let createPerson name age =
    result {
        let! validName = validateName name
        let! validAge = validateAge age
        return { Name = validName; Age = validAge }
    }

After (Roc):

ValidationError : [InvalidName, InvalidAge]

Person : { name : Str, age : I64 }

validateName : Str -> Result Str [InvalidName]
validateName = \name ->
    if Str.isEmpty(name) then
        Err(InvalidName)
    else
        Ok(name)

validateAge : I64 -> Result I64 [InvalidAge]
validateAge = \age ->
    if age < 0 || age > 120 then
        Err(InvalidAge)
    else
        Ok(age)

createPerson : Str, I64 -> Result Person [InvalidName, InvalidAge]
createPerson = \name, age ->
    validName = validateName!(name)
    validAge = validateAge!(age)
    Ok({ name: validName, age: validAge })

Example 3: Complex - Async File Processing

Before (F#):

open System.IO

type ProcessingError =
    | FileNotFound of string
    | InvalidFormat of string

let readFile path = async {
    try
        let! content = File.ReadAllTextAsync(path) |> Async.AwaitTask
        return Ok content
    with
    | :? FileNotFoundException ->
        return Error (FileNotFound path)
}

let processContent content =
    if content.Contains("error") then
        Error (InvalidFormat "Content contains error")
    else
        Ok (content.ToUpper())

let writeFile path content = async {
    do! File.WriteAllTextAsync(path, content) |> Async.AwaitTask
    return Ok ()
}

let processFile inputPath outputPath = async {
    let! contentResult = readFile inputPath
    match contentResult with
    | Error e -> return Error e
    | Ok content ->
        match processContent content with
        | Error e -> return Error e
        | Ok processed ->
            return! writeFile outputPath processed
}

// Usage
let result =
    processFile "input.txt" "output.txt"
    |> Async.RunSynchronously

After (Roc):

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

import pf.File
import pf.Path
import pf.Task exposing [Task]
import pf.Stdout

ProcessingError : [FileNotFound Str, InvalidFormat Str]

readFile : Str -> Task Str [FileReadErr Path.ReadErr]*
readFile = \path ->
    File.readUtf8(Path.fromStr(path))

processContent : Str -> Result Str [InvalidFormat Str]
processContent = \content ->
    if Str.contains(content, "error") then
        Err(InvalidFormat("Content contains error"))
    else
        Ok(Str.toUpper(content))

writeFile : Str, Str -> Task {} [FileWriteErr Path.WriteErr]*
writeFile = \path, content ->
    File.writeUtf8(Path.fromStr(path), content)

processFile : Str, Str -> Task {} [FileReadErr Path.ReadErr, InvalidFormat Str, FileWriteErr Path.WriteErr]*
processFile = \inputPath, outputPath ->
    # Read file (returns Task)
    content = readFile!(inputPath)

    # Process content (pure function, returns Result)
    processed = processContent!(content)

    # Write file (returns Task)
    writeFile!(outputPath, processed)

main : Task {} []
main =
    when processFile("input.txt", "output.txt") is
        Ok({}) -> Stdout.line!("File processed successfully")
        Err(FileReadErr(_)) -> Stdout.line!("Error reading file")
        Err(InvalidFormat(msg)) -> Stdout.line!("Invalid format: \(msg)")
        Err(FileWriteErr(_)) -> Stdout.line!("Error writing file")

Key conversions:

  • F# Async<'a> → Roc Task a err (platform-provided)
  • F# try/​with → Roc Result type with pattern matching
  • F# computation expression → Roc ! try operator
  • F# can mix pure/​async; Roc separates Task boundaries

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational patterns with cross-language examples
  • convert-elm-clojure - Another functional language pair conversion (similar paradigm shifts)
  • lang-fsharp-dev - F# development patterns
  • lang-roc-dev - Roc development patterns

Cross-cutting pattern skills:

  • patterns-concurrency-dev - Async workflows, Task model across languages
  • patterns-serialization-dev - JSON, validation across languages
  • patterns-metaprogramming-dev - Type providers vs code generation

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View