Agent Skills: Erlang to F# Conversion

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.

UncategorizedID: arustydev/ai/convert-erlang-fsharp

Repository

aRustyDevLicense: AGPL-3.0
72

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aRustyDev/agents/tree/HEAD/content/skills/convert-erlang-fsharp

Skill Files

Browse the full folder contents for convert-erlang-fsharp.

Download Skill

Loading file tree…

content/skills/convert-erlang-fsharp/SKILL.md

Skill Metadata

Name
convert-erlang-fsharp
Description
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.

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 | |--------|-----|-------| | atom() | string or discriminated union | Atoms become strings or DU cases | | integer() | int or int64 | Fixed-size integers | | float() | float | 64-bit double | | binary() | byte[] | Byte arrays | | list() | 'a list | Immutable lists | | tuple() | 'a * 'b | Product types | | map() | Map<'k,'v> | Immutable maps | | pid() | MailboxProcessor<'T> | Actor-style processing | | fun() | 'a -> 'b | 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

  • lang-erlang-dev - Erlang development patterns
  • lang-fsharp-dev - F# development patterns
  • meta-convert-dev - General conversion methodology