Agents convert-erlang-fsharp
Bidirectional conversion between Erlang and Fsharp. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Erlang↔Fsharp specific patterns.
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/convert-erlang-fsharp" ~/.claude/skills/arustydev-agents-convert-erlang-fsharp && rm -rf "$T"
manifest:
content/skills/convert-erlang-fsharp/SKILL.mdsource content
Erlang to F# Conversion
Overview
This skill guides the conversion of Erlang code to idiomatic F# while maintaining functional programming principles, concurrent programming patterns, and fault-tolerance capabilities. F# provides strong functional programming support with .NET integration.
Key Language Differences
Type Systems
- Erlang: Dynamic typing with pattern matching
- F#: Static typing with type inference, algebraic data types
Concurrency Models
- Erlang: Actor model with lightweight processes, message passing
- F#: MailboxProcessor (similar to actors), async workflows, Task Parallel Library
Runtime Environment
- Erlang: BEAM VM with hot code swapping, distributed computing
- F#: .NET CLR with async/await, comprehensive standard library
Quick Reference
| Erlang | F# | Notes |
|---|---|---|
| or discriminated union | Atoms become strings or DU cases |
| or | Fixed-size integers |
| | 64-bit double |
| | Byte arrays |
| | Immutable lists |
| | Product types |
| | Immutable maps |
| | Actor-style processing |
| | First-class functions |
Core Conversion Patterns
1. Module and Function Definitions
Erlang:
-module(calculator). -export([add/2, multiply/2]). add(X, Y) -> X + Y. multiply(X, Y) -> X * Y.
F#:
module Calculator = let add x y = x + y let multiply x y = x * y
2. Pattern Matching
Erlang:
factorial(0) -> 1; factorial(N) when N > 0 -> N * factorial(N - 1).
F#:
let rec factorial n = match n with | 0 -> 1 | n when n > 0 -> n * factorial (n - 1) | _ -> failwith "Negative input not allowed"
3. Records and Types
Erlang:
-record(person, {name, age, email}). create_person(Name, Age, Email) -> #person{name=Name, age=Age, email=Email}.
F#:
type Person = { Name: string Age: int Email: string } let createPerson name age email = { Name = name; Age = age; Email = email }
4. Lists and Comprehensions
Erlang:
double_list(List) -> [X * 2 || X <- List]. filter_even(List) -> [X || X <- List, X rem 2 =:= 0].
F#:
let doubleList list = list |> List.map ((*) 2) let filterEven list = list |> List.filter (fun x -> x % 2 = 0)
5. Actor Model / Process Communication
Erlang:
-module(counter). start() -> spawn(fun() -> loop(0) end). increment(Pid) -> Pid ! {increment, self()}, receive {ok, NewValue} -> NewValue end. loop(Count) -> receive {increment, From} -> NewCount = Count + 1, From ! {ok, NewCount}, loop(NewCount) end.
F#:
module Counter = type Message = | Increment of AsyncReplyChannel<int> type CounterAgent() = let agent = MailboxProcessor.Start(fun inbox -> let rec loop count = async { let! msg = inbox.Receive() match msg with | Increment(reply) -> let newCount = count + 1 reply.Reply(newCount) return! loop newCount } loop 0 ) member _.Increment() = agent.PostAndReply(Increment) let start() = CounterAgent()
6. gen_server Pattern
Erlang:
-module(my_server). -behaviour(gen_server). handle_call(get_count, _From, State = #{count := Count}) -> {reply, Count, State}; handle_call({increment, N}, _From, State = #{count := Count}) -> NewState = State#{count := Count + N}, {reply, ok, NewState}.
F#:
module MyServer = type Message = | GetCount of AsyncReplyChannel<int> | Increment of int * AsyncReplyChannel<unit> type State = { Count: int } type ServerAgent() = let agent = MailboxProcessor.Start(fun inbox -> let rec loop state = async { let! msg = inbox.Receive() match msg with | GetCount(reply) -> reply.Reply(state.Count) return! loop state | Increment(n, reply) -> let newState = { Count = state.Count + n } reply.Reply() return! loop newState } loop { Count = 0 } ) member _.GetCount() = agent.PostAndReply(GetCount) member _.Increment(n) = agent.PostAndReply(fun ch -> Increment(n, ch))
7. Error Handling
Erlang:
safe_divide(_, 0) -> {error, division_by_zero}; safe_divide(X, Y) -> {ok, X / Y}.
F#:
type DivisionError = DivisionByZero let safeDivide x y = match y with | 0 -> Error DivisionByZero | _ -> Ok (x / y) // Or using Option let safeDivide' x y = match y with | 0 -> None | _ -> Some (x / y)
8. Binary Pattern Matching
Erlang:
parse_header(<<Type:8, Length:16, Rest/binary>>) -> {Type, Length, Rest}.
F#:
let parseHeader (bytes: byte[]) = if bytes.Length < 3 then None else let type' = bytes.[0] let length = (uint16 bytes.[1] <<< 8) ||| uint16 bytes.[2] let rest = bytes.[3..] Some (type', length, rest)
9. ETS Tables to .NET Collections
Erlang:
store(Key, Value) -> ets:insert(my_table, {Key, Value}). lookup(Key) -> case ets:lookup(my_table, Key) of [{Key, Value}] -> {ok, Value}; [] -> {error, not_found} end.
F#:
module MyTable = open System.Collections.Concurrent let private table = ConcurrentDictionary<string, obj>() let store key value = table.[key] <- value let lookup key = match table.TryGetValue(key) with | true, value -> Some value | false, _ -> None
Common Libraries and Equivalents
| Erlang | F# / .NET Equivalent |
|---|---|
| gen_server | MailboxProcessor, Akka.NET actors |
| supervisor | Custom supervision, Akka.NET |
| ETS | System.Collections.Concurrent |
| httpc | System.Net.Http, FSharp.Data.Http |
| jsx (JSON) | System.Text.Json, FSharp.Json |
| cowboy (HTTP) | ASP.NET Core, Giraffe, Suave |
| lager (logging) | Serilog, NLog |
Best Practices
1. Embrace Static Typing
- Use F#'s type system to catch errors at compile time
- Define explicit types for domain models
- Use discriminated unions for state machines
2. Preserve Functional Patterns
- Keep functions pure where possible
- Use immutable data structures
- Leverage F# pipeline operators (|>)
3. Adapt Concurrency Models
- Use MailboxProcessor for actor-like behavior
- Consider Akka.NET for complex distributed systems
- Use async workflows for I/O-bound operations
4. Error Handling
- Prefer Option and Result types over exceptions
- Use computation expressions for error propagation
- Reserve exceptions for truly exceptional cases
Migration Strategy
Step 1: Analyze Erlang Codebase
- Identify module structure and dependencies
- Map OTP behaviors (gen_server, supervisor)
- Document message-passing patterns
Step 2: Design F# Architecture
- Plan module organization
- Design type hierarchy for records and unions
- Choose concurrency approach
Step 3: Convert Core Logic
- Start with pure functions and data structures
- Convert pattern matching and recursion
- Translate list operations
Step 4: Implement Concurrency
- Replace spawn/receive with MailboxProcessor
- Convert gen_server to agent-based patterns
- Add async workflows for I/O operations
Step 5: Testing and Validation
- Port EUnit tests to FsUnit or xUnit
- Test concurrent behaviors
- Performance testing and optimization
See Also
- Erlang development patternslang-erlang-dev
- F# development patternslang-fsharp-dev
- General conversion methodologymeta-convert-dev