Agents meta-convert-dev
Create language conversion skills for translating code from language A to language B. Use when building 'convert-X-Y' skills, designing type mappings between languages, establishing idiom translation patterns, or defining conversion methodologies. Provides foundational patterns that specific conversion skills extend.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/meta-convert-dev" ~/.claude/skills/arustydev-agents-meta-convert-dev && rm -rf "$T"
content/skills/meta-convert-dev/SKILL.mdLanguage Conversion Skill Development
Foundational patterns for creating one-way language conversion skills. This meta-skill guides the creation of
convert-X-Y skills (e.g., convert-typescript-rust, convert-python-golang) that assist in translating/refactoring code from a source language to a target language.
When to Use This Skill
- Creating a new
skill for language translationconvert-X-Y - Designing type system mappings between languages
- Establishing idiom translation strategies
- Defining conversion workflows and validation approaches
- Building tooling recommendations for transpilation
This Skill Does NOT Cover
- Actual code conversion (use specific
skills orconvert-X-Y
)meta-convert-guide - Language tutorials (see
skills)lang-*-dev - Bidirectional translation (each direction is a separate skill)
- Runtime interop/FFI (see language-specific interop skills)
Related Skills
- Patterns and strategies for performing conversionsmeta-convert-guide
skills - Language-pair specific conversion skillsconvert-*
Existing Conversion Skills
For concrete, language-pair-specific examples, see these skills:
| Skill | Description |
|---|---|
| TypeScript → Rust (GC → ownership, exceptions → Result) |
| TypeScript → Python (static → dynamic typing) |
| TypeScript → Go (OOP → simplicity, Promise → goroutine) |
| Go → Rust (GC → ownership, interface → trait) |
| Python → Rust (dynamic → static, GC → ownership) |
| Python → F# (dynamic → static, OOP → functional) |
| Python → Erlang (single-threaded → BEAM actors) |
| Python → Clojure (imperative → functional, OOP → data-oriented) |
| Clojure → Roc (JVM → native, dynamic → static) |
| Clojure → Elixir (JVM → BEAM, STM → actors) |
| Clojure → Haskell (dynamic → static, practical → pure) |
Note: This list may not be complete. Search
content/skills/convert-* for all available conversion skills.
Skill Categories
| Category | Examples | Key Challenges |
|---|---|---|
| Static → Static | TypeScript→Go, Rust→Go | Type system mapping, idiom differences |
| Dynamic → Static | Python→Rust, Clojure→Haskell | Add types, handle runtime flexibility |
| Static → Dynamic | TypeScript→Python, Go→Elixir | Remove type annotations, embrace flexibility |
| Dynamic → Dynamic | Python→Clojure, Clojure→Elixir | Paradigm and runtime differences |
| OOP → Functional | Java→Clojure, Python→Haskell | Replace classes with data+functions |
| Functional → Functional | Clojure→Elixir, Haskell→Scala | FP dialect differences |
| GC → Ownership | Any→Rust | Add explicit lifetimes and borrowing |
| Platform Migration | JVM→BEAM, JVM→Native | Runtime semantics, library ecosystem |
When creating a new conversion skill, refer to the specific
convert-X-Y skills for production-ready examples.
Platform Ecosystem Considerations
When converting between languages on different platforms (JVM, BEAM, .NET, Native), consider:
| Platform | Key Characteristics |
|---|---|
| JVM | Bytecode, JIT, GC, thread-based concurrency, rich stdlib |
| BEAM | Lightweight processes, preemptive scheduling, fault tolerance, hot reload |
| .NET | Similar to JVM, async/await primitives, strong Windows integration |
| Native | Direct compilation, manual/ownership memory, no runtime overhead |
| Scripting | Interpreted/JIT, dynamic typing, GIL (Python) |
See: Platform Ecosystem Reference for detailed runtime characteristics, stdlib mapping strategies, transpiler options, and FFI considerations.
Paradigm Translation Patterns
Cross-paradigm conversions require mental model shifts:
| Source → Target | Key Transformation |
|---|---|
| OOP → FP | Classes → data + functions, mutation → immutable updates |
| Imperative → Declarative | Loops → higher-order functions, statements → expressions |
| Dynamic → Static | Add type annotations, handle runtime flexibility statically |
| Mutable-first → Immutable-first | State threading, persistent data structures |
See: Paradigm Translation Reference for detailed patterns and examples.
Additional Reference Materials
| Topic | Reference File |
|---|---|
| Numeric edge cases | references/numeric-edge-cases.md - Overflow handling, division semantics, float precision |
| Stdlib mapping | references/stdlib-mapping.md - Finding equivalent functions across languages |
| Build systems | references/build-system-mapping.md - Package managers and project structure |
| Module systems | references/module-system-comparison.md - Import/export patterns |
Conversion Skill Naming Convention
convert-<source>-<target>
| Component | Description | Example |
|---|---|---|
| Fixed prefix | |
| Source language (lowercase) | , , |
| Target language (lowercase) | , , |
Examples:
- TypeScript → Rustconvert-typescript-rust
- Python → Goconvert-python-golang
- Go → Rustconvert-golang-rust
Note: Each skill is ONE-WAY.
convert-A-B and convert-B-A are separate skills with different patterns.
Conversion Skill Structure
Every
convert-X-Y skill should follow this structure:
# Convert <Source> to <Target> ## Overview Brief description of the conversion, common use cases, and what to expect. ## When to Use - Scenarios where this conversion makes sense - Benefits of the target language for this use case ## When NOT to Use - Scenarios where conversion is not recommended - Better alternatives ## Type System Mapping Complete mapping table from source types to target types. ## Idiom Translation Source patterns and their idiomatic target equivalents. ## Error Handling How to convert error handling patterns. ## Concurrency Patterns How async/threading models translate. ## Memory & Ownership If applicable, how memory models differ and translate. ## Testing Strategy How to verify functional equivalence. ## Tooling Available tools, transpilers, and validation helpers. ## Common Pitfalls Mistakes to avoid during conversion. ## Examples Concrete before/after conversion examples.
The 8 Pillars Framework
Every conversion skill should address these 8 pillars for comprehensive coverage:
| Pillar | What to Document |
|---|---|
| 1. Module System | Import/export, visibility, package structure |
| 2. Error Handling | Exception/Result/Option patterns, error propagation |
| 3. Concurrency | Async/await, threads, processes, channels |
| 4. Metaprogramming | Macros, decorators, reflection, code generation |
| 5. Zero/Default Values | Null handling, default initialization, optional types |
| 6. Serialization | JSON, binary formats, validation patterns |
| 7. Build System | Package managers, build tools, project structure |
| 8. Testing | Test frameworks, property-based testing, verification |
9th Pillar: Dev Workflow (for REPL-centric languages)
For conversions involving REPL-centric languages (Clojure, Elixir, Erlang, Haskell, Lisp, F#), add:
| Pillar | What to Document |
|---|---|
| 9. Dev Workflow & REPL | REPL patterns, hot reload, interactive debugging |
See
meta-convert-guide for detailed guidance on each pillar.
Skill Creation Workflow
When creating a new
convert-X-Y skill:
1. Assess the Conversion
- Check for existing skill: Search
content/skills/convert-* - Check for reverse skill: If creating
, check ifconvert-A-B
existsconvert-B-A - Identify category: Static→Dynamic, GC→Ownership, etc.
- Estimate difficulty: See difficulty-matrix.md for pre-calculated ratings
2. Gather Language Knowledge
- Read
skill for source languagelang-X-dev - Read
skill for target languagelang-Y-dev - Identify key differences in the 8 pillars
3. Create Type Mapping Tables
For each pillar, create comprehensive mapping tables:
| Source (X) | Target (Y) | Notes | |------------|------------|-------| | `string` | `String` | ... |
4. Document Idiom Translations
Show idiomatic translations, not literal ports:
// Source: X [source code] // Target: Y (idiomatic) [target code] // Avoid: Y (transliterated) [non-idiomatic code]
5. Include Testing Strategy
- Port existing tests first
- Add property-based tests for invariants
- Use golden testing for output comparison
6. Self-Review Checklist
Before finalizing the skill:
- All 8 pillars addressed (9 if REPL-centric)
- Type mappings are complete
- Examples are idiomatic, not transliterated
- Common pitfalls documented
- Testing strategy included
- Cross-references to related skills
Concurrency Pattern Translation
When converting code between languages, concurrency models are often the most challenging aspect. Different languages embody fundamentally different concurrency philosophies, from shared-memory threads to isolated processes, from promises to channels, from Software Transactional Memory (STM) to actor models.
This section provides a comprehensive guide to translating concurrency patterns across languages.
Concurrency Model Matrix
Understanding the mapping between source and target concurrency models is crucial for successful conversion:
| Source Model | Target Model | Translation Strategy | Key Considerations |
|---|---|---|---|
| Actors (BEAM) | STM (Clojure) | Use atoms/refs for state, core.async for message-passing | BEAM processes → atoms for simple state, refs+dosync for coordinated updates |
| Actors (BEAM) | IO Monad (Haskell) | Use TVar for shared state, async for spawning | GenServers → IORef or TVar, supervision → exception handling |
| Actors (BEAM) | Goroutines (Go) | Channels for message passing, structs for state | GenServer → goroutine with channel, supervisor → error handling |
| STM (Clojure) | Actors (BEAM) | Wrap refs in GenServer, use supervisor for coordination | dosync transactions → GenServer serializes updates |
| STM (Clojure) | Goroutines (Go) | Channels + select for coordination, mutexes for shared state | refs → channels or sync.Mutex, atoms → atomic package |
| Channels (Go) | Actors (BEAM) | GenStage/Flow for backpressure, GenServer for state | Buffered channels → GenServer queue, select → receive |
| Channels (Go) | Promises (JS/TS) | Promise wraps channel receive, async/await for coordination | Goroutine → async function, channel receive → await |
| IO Monad (Haskell) | Tasks (Roc) | Map IO actions to Task effects | IO-based concurrency → Task-based effects system |
| Promises (JS/TS) | Goroutines (Go) | Goroutines for async execution, channels for communication | async function → goroutine, Promise → channel or direct return |
| Promises (JS/TS) | Futures (Rust) | Tokio runtime for async, futures for lazy evaluation | Promise (eager) → Future (lazy), await → .await |
| Threads (Python) | Actors (BEAM) | Spawn processes for each thread, message passing for communication | GIL limitations → true parallelism, threading.Thread → spawn |
| Asyncio (Python) | Async/Await (Rust) | Tokio runtime, async functions, futures | Event loop → runtime, coroutines → async fn |
Key Insight: Most conversions require not just syntactic translation but a fundamental shift in how you think about concurrency. For example:
- Shared memory (threads) → Message passing (actors): Stop thinking about locks; start thinking about process isolation
- Eager execution (Promises) → Lazy evaluation (Futures): Understand when work actually starts
- Preemptive scheduling (OS threads) → Cooperative scheduling (async/await): Be aware of blocking operations
Pattern Translation Examples
Example 1: Clojure STM → Elixir Actors (Coordinated State Updates)
Problem: Clojure's STM allows multiple refs to be updated atomically in a transaction. Elixir's actor model has isolated processes.
Clojure (Source):
(def account-a (ref 1000)) (def account-b (ref 500)) (defn transfer [from to amount] (dosync (alter from - amount) (alter to + amount))) ;; Atomic bank transfer (transfer account-a account-b 100) ;; account-a: 900, account-b: 600
Elixir (Target - Idiomatic Translation):
defmodule Bank do use GenServer # Client API def start_link(initial_accounts) do GenServer.start_link(__MODULE__, initial_accounts, name: __MODULE__) end def transfer(from_account, to_account, amount) do GenServer.call(__MODULE__, {:transfer, from_account, to_account, amount}) end def get_balance(account) do GenServer.call(__MODULE__, {:get_balance, account}) end # Server Callbacks @impl true def init(initial_accounts) do {:ok, initial_accounts} end @impl true def handle_call({:transfer, from, to, amount}, _from, accounts) do case accounts do %{^from => from_balance, ^to => to_balance} when from_balance >= amount -> updated_accounts = accounts |> Map.update!(from, &(&1 - amount)) |> Map.update!(to, &(&1 + amount)) {:reply, :ok, updated_accounts} _ -> {:reply, {:error, :insufficient_funds}, accounts} end end @impl true def handle_call({:get_balance, account}, _from, accounts) do {:reply, Map.get(accounts, account, 0), accounts} end end # Usage {:ok, _pid} = Bank.start_link(%{account_a: 1000, account_b: 500}) :ok = Bank.transfer(:account_a, :account_b, 100) # account_a: 900, account_b: 600
Translation Strategy:
- Centralize coordinated state: All accounts in single GenServer (serializes transactions)
- Transaction semantics: GenServer's synchronous call provides atomicity
- Pattern matching: Use guards to validate sufficient funds before update
- Immutable updates: Map.update! returns new map, pattern matches for validation
Edge Cases & Gotchas:
- Deadlock prevention: Single GenServer eliminates distributed deadlocks
- Scalability tradeoff: STM allows concurrent reads; GenServer serializes all operations
- Alternative: For high throughput, shard accounts across multiple GenServers with distributed coordination
Performance Implications:
- Clojure STM: Optimistic concurrency (retry on conflict), parallel reads
- Elixir GenServer: Pessimistic serialization, all operations sequential
- For read-heavy workloads, consider Agent for each account + coordinator for transfers
Cross-reference: See
convert-clojure-elixir for complete STM → Actor translation patterns.
Example 2: Go Channels → TypeScript Promises (Backpressure-Aware Pipeline)
Problem: Go channels provide natural backpressure through blocking sends/receives. JavaScript Promises execute eagerly and don't support backpressure natively.
Go (Source):
func processStream(items []string) <-chan string { out := make(chan string, 10) // Buffered channel go func() { defer close(out) for _, item := range items { // Simulate slow processing time.Sleep(100 * time.Millisecond) processed := strings.ToUpper(item) out <- processed // Blocks if buffer full (backpressure!) } }() return out } func main() { results := processStream([]string{"a", "b", "c", "d", "e"}) for result := range results { fmt.Println("Got:", result) time.Sleep(200 * time.Millisecond) // Slow consumer } }
TypeScript (Target - Idiomatic Translation):
async function* processStream(items: string[]): AsyncGenerator<string> { for (const item of items) { // Simulate slow processing await new Promise(resolve => setTimeout(resolve, 100)); const processed = item.toUpperCase(); yield processed; // Yields control back to consumer } } async function main() { const items = ["a", "b", "c", "d", "e"]; for await (const result of processStream(items)) { console.log("Got:", result); await new Promise(resolve => setTimeout(resolve, 200)); // Slow consumer } } main();
Alternative (Explicit Promise Queue with Backpressure):
class Channel<T> { private queue: T[] = []; private waiting: ((value: T) => void)[] = []; private maxSize: number; constructor(bufferSize: number = 10) { this.maxSize = bufferSize; } async send(value: T): Promise<void> { // Backpressure: wait if queue is full while (this.queue.length >= this.maxSize) { await new Promise(resolve => setTimeout(resolve, 10)); } if (this.waiting.length > 0) { const resolve = this.waiting.shift()!; resolve(value); } else { this.queue.push(value); } } async receive(): Promise<T> { if (this.queue.length > 0) { return this.queue.shift()!; } return new Promise<T>(resolve => { this.waiting.push(resolve); }); } } async function processStreamWithChannel(items: string[]): Promise<Channel<string>> { const ch = new Channel<string>(10); (async () => { for (const item of items) { await new Promise(resolve => setTimeout(resolve, 100)); await ch.send(item.toUpperCase()); } })(); return ch; }
Translation Strategy:
- Async generators: Most idiomatic for streaming data in TypeScript
- Yield control:
allows consumer to control pace (like channel receive blocking)yield - Custom Channel class: For explicit buffering and backpressure semantics
- Event-driven alternative: Use Node.js streams for larger data pipelines
Edge Cases & Gotchas:
- No native backpressure: Promises execute eagerly; must implement queue manually
- Memory growth: Without backpressure, fast producer overwhelms slow consumer
- Cancellation: Go channels close; async generators must handle cleanup explicitly
- Error propagation: Go errors via multiple returns; TypeScript uses try/catch or rejected promises
Performance Implications:
- Go channels: OS-level blocking, true concurrency across cores
- TypeScript async: Event loop, single-threaded unless using worker threads
- For CPU-bound work, consider worker threads or external processing
Cross-reference: See
convert-typescript-golang and convert-golang-rust for channel translation patterns.
Example 3: Python Asyncio → Erlang Processes (Event Loop → Actor Model)
Problem: Python's asyncio uses a single-threaded event loop with coroutines. Erlang uses lightweight processes with true preemptive concurrency.
Python (Source):
import asyncio from typing import Dict class ConnectionPool: def __init__(self, max_connections: int = 10): self.max_connections = max_connections self.active_connections: Dict[str, asyncio.Task] = {} self.semaphore = asyncio.Semaphore(max_connections) async def connect(self, conn_id: str): async with self.semaphore: print(f"Connecting {conn_id}") await asyncio.sleep(1) # Simulate connection self.active_connections[conn_id] = asyncio.current_task() print(f"Connected {conn_id}") return conn_id async def disconnect(self, conn_id: str): if conn_id in self.active_connections: print(f"Disconnecting {conn_id}") del self.active_connections[conn_id] async def main(): pool = ConnectionPool(max_connections=3) # Start 5 connections (only 3 concurrent due to semaphore) tasks = [pool.connect(f"conn-{i}") for i in range(5)] await asyncio.gather(*tasks) asyncio.run(main())
Erlang (Target - Idiomatic Translation):
-module(connection_pool). -behaviour(gen_server). %% API -export([start_link/1, connect/2, disconnect/2, stop/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -record(state, { max_connections :: integer(), active_connections :: #{binary() => pid()}, waiting_queue :: queue:queue() }). %% Client API start_link(MaxConnections) -> gen_server:start_link(?MODULE, MaxConnections, []). connect(PoolPid, ConnId) -> gen_server:call(PoolPid, {connect, ConnId}, infinity). disconnect(PoolPid, ConnId) -> gen_server:cast(PoolPid, {disconnect, ConnId}). stop(PoolPid) -> gen_server:stop(PoolPid). %% Server Callbacks init(MaxConnections) -> State = #state{ max_connections = MaxConnections, active_connections = #{}, waiting_queue = queue:new() }, {ok, State}. handle_call({connect, ConnId}, From, State) -> #state{ max_connections = Max, active_connections = Active, waiting_queue = Queue } = State, case maps:size(Active) < Max of true -> %% Spawn connection process Pid = spawn_link(fun() -> connection_worker(ConnId) end), io:format("Connecting ~s~n", [ConnId]), NewActive = maps:put(ConnId, Pid, Active), {reply, {ok, ConnId}, State#state{active_connections = NewActive}}; false -> %% Queue is full, add to waiting queue NewQueue = queue:in({ConnId, From}, Queue), {noreply, State#state{waiting_queue = NewQueue}} end. handle_cast({disconnect, ConnId}, State) -> #state{ active_connections = Active, waiting_queue = Queue } = State, io:format("Disconnecting ~s~n", [ConnId]), NewActive = maps:remove(ConnId, Active), %% Process waiting queue case queue:out(Queue) of {{value, {NextConnId, From}}, NewQueue} -> Pid = spawn_link(fun() -> connection_worker(NextConnId) end), gen_server:reply(From, {ok, NextConnId}), FinalActive = maps:put(NextConnId, Pid, NewActive), {noreply, State#state{ active_connections = FinalActive, waiting_queue = NewQueue }}; {empty, _} -> {noreply, State#state{active_connections = NewActive}} end. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. %% Internal Functions connection_worker(ConnId) -> timer:sleep(1000), %% Simulate connection io:format("Connected ~s~n", [binary_to_list(ConnId)]), receive disconnect -> ok end. %% Usage example %% 1> {ok, Pool} = connection_pool:start_link(3). %% 2> [connection_pool:connect(Pool, list_to_binary("conn-" ++ integer_to_list(I))) || I <- lists:seq(1, 5)].
Translation Strategy:
- Event loop → GenServer: Central coordinator manages connection state
- Semaphore → Capacity check: Manual tracking of active connections vs max
- Coroutines → Processes: Each connection is a separate process (true isolation)
- Async tasks → spawn_link: Lightweight processes with supervision
- Queue for backpressure: Explicitly manage waiting clients when pool full
Edge Cases & Gotchas:
- Blocking vs message passing: Python blocks on semaphore; Erlang queues requests
- Error handling: Python exceptions in event loop; Erlang links and monitors for crash detection
- Process cleanup: Must handle process termination and queue cleanup
- Timeout handling: Add receive timeout patterns for connection timeouts
Performance Implications:
- Python asyncio: Single thread, cooperative scheduling, GIL limitations
- Erlang: True concurrency, processes scheduled across cores, no GIL
- Erlang's preemptive scheduling handles blocking better (processes don't block each other)
- Consider supervision tree for fault tolerance in Erlang
Cross-reference: See
convert-python-erlang for complete asyncio → BEAM translation patterns.
Supervision and Fault Tolerance Translation
Supervision is a key pattern in fault-tolerant systems. Different concurrency models provide different supervision mechanisms:
| Source Model | Target Model | Translation Pattern |
|---|---|---|
| BEAM Supervision Trees | Go error handling | Explicit error returns, retry loops, health checks |
| BEAM Supervision Trees | Rust Result + retry crate | Result types for errors, tokio::task for spawning, manual restart |
| BEAM let-it-crash | Clojure agents + error handlers | Agent error-handler and error-mode, restart-agent |
| Go defer/panic/recover | BEAM try/catch + supervisor | Convert panic → exit, recover → supervisor restart |
| Python try/except | BEAM let-it-crash | Remove defensive error handling, use supervisor for recovery |
| Rust panic=abort | BEAM supervision | Change panic strategy to supervised process restart |
Supervision Pattern: Erlang Supervision Tree → Go Error Handling
Erlang (Source - Let It Crash):
-module(worker_sup). -behaviour(supervisor). -export([start_link/0, init/1]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> SupFlags = #{ strategy => one_for_one, %% Restart individual workers intensity => 5, %% Max 5 restarts period => 60 %% In 60 seconds }, ChildSpecs = [ #{ id => worker1, start => {worker, start_link, [worker1]}, restart => permanent, %% Always restart shutdown => 5000, type => worker } ], {ok, {SupFlags, ChildSpecs}}. %% Worker module (crashes are OK, supervisor restarts) -module(worker). -behaviour(gen_server). handle_call({process, Data}, _From, State) -> Result = risky_operation(Data), %% May crash - that's fine! {reply, Result, State}. risky_operation(Data) -> case Data of invalid -> error(badarg); %% Crash - supervisor will restart _ -> process_data(Data) end.
Go (Target - Explicit Error Handling + Retry):
package main import ( "context" "errors" "fmt" "log" "time" ) // WorkerSupervisor manages worker lifecycle with restart logic type WorkerSupervisor struct { maxRestarts int period time.Duration restartLog []time.Time workerFunc func(context.Context) error ctx context.Context cancel context.CancelFunc } func NewWorkerSupervisor( maxRestarts int, period time.Duration, workerFunc func(context.Context) error, ) *WorkerSupervisor { ctx, cancel := context.WithCancel(context.Background()) return &WorkerSupervisor{ maxRestarts: maxRestarts, period: period, workerFunc: workerFunc, restartLog: make([]time.Time, 0), ctx: ctx, cancel: cancel, } } func (s *WorkerSupervisor) Start() { go s.supervise() } func (s *WorkerSupervisor) Stop() { s.cancel() } func (s *WorkerSupervisor) supervise() { for { select { case <-s.ctx.Done(): log.Println("Supervisor shutting down") return default: // Check restart intensity if s.exceedsRestartIntensity() { log.Println("Max restart intensity exceeded, shutting down") return } // Run worker err := s.workerFunc(s.ctx) if err != nil { log.Printf("Worker crashed: %v, restarting...", err) s.logRestart() time.Sleep(1 * time.Second) // Backoff before restart } } } } func (s *WorkerSupervisor) exceedsRestartIntensity() bool { now := time.Now() cutoff := now.Add(-s.period) // Remove old restart entries validRestarts := make([]time.Time, 0) for _, t := range s.restartLog { if t.After(cutoff) { validRestarts = append(validRestarts, t) } } s.restartLog = validRestarts return len(s.restartLog) >= s.maxRestarts } func (s *WorkerSupervisor) logRestart() { s.restartLog = append(s.restartLog, time.Now()) } // Worker implementation type Worker struct { id string } func (w *Worker) Run(ctx context.Context) error { for { select { case <-ctx.Done(): return ctx.Err() default: // Simulate work with potential errors if err := w.riskyOperation(); err != nil { return fmt.Errorf("worker %s failed: %w", w.id, err) } time.Sleep(1 * time.Second) } } } func (w *Worker) riskyOperation() error { // Simulate occasional failures if time.Now().Unix()%10 == 0 { return errors.New("random failure") } log.Printf("Worker %s processing...", w.id) return nil } func main() { worker := &Worker{id: "worker1"} supervisor := NewWorkerSupervisor( 5, // Max 5 restarts 60*time.Second, // In 60 seconds worker.Run, ) supervisor.Start() // Run for demo duration time.Sleep(120 * time.Second) supervisor.Stop() }
Translation Strategy:
- Supervisor behavior → Supervisor struct: Encapsulate restart logic in struct
- Restart strategies: Implement one-for-one manually (each worker independent)
- Intensity tracking: Log restart times, check against intensity limits
- Context for cancellation: Use context.Context for graceful shutdown
- Error propagation: Workers return errors instead of crashing
Supervision Translation Rules:
→ Independent goroutines with individual supervisorsone_for_one
→ Cancel all workers if one fails, restart allone_for_all
→ Cancel dependent workers (those started after failed worker)rest_for_one- Restart intensity → Track restart timestamps, check against limits
Error Isolation Strategies:
| Language | Isolation Mechanism | Fault Containment |
|---|---|---|
| Erlang/Elixir | Separate processes, supervision trees | Process crash doesn't affect others |
| Go | Goroutines + defer/recover, error returns | Panic in goroutine kills goroutine only (with recover) |
| Rust | Result/Option types, panic=abort or unwind | Thread panic isolated, Result forces error handling |
| Clojure | Agents with error-mode, futures with deref | Agent errors caught in error-handler |
| Haskell | Async with catches, STM for atomicity | Exceptions in async isolated, catchable |
| Python | Threading/asyncio with try/except | Thread exceptions isolated, asyncio tasks can be cancelled |
Cross-reference:
- See
for asyncio → supervision patternsconvert-python-erlang - See
for panic/recover → Result type patternsconvert-golang-rust - See
for agent error handling → OTP patternsconvert-clojure-elixir
Common Concurrency Anti-Patterns to Avoid
When translating concurrency patterns, avoid these common mistakes:
-
Literal Translation of Synchronization Primitives
- ❌ Don't translate
in Python tolock()
in every target languageMutex - ✓ Use target's idiomatic concurrency: actors for Erlang, STM for Clojure, channels for Go
- ❌ Don't translate
-
Ignoring Backpressure
- ❌ Don't translate bounded channels to unbounded Promises (memory leak!)
- ✓ Implement explicit backpressure with async generators or custom queues
-
Mixing Concurrency Models
- ❌ Don't mix threads + async/await without understanding implications
- ✓ Choose one concurrency model and use it consistently
-
Over-Serialization
- ❌ Don't funnel all parallel operations through a single actor/GenServer
- ✓ Shard state across multiple actors/processes for scalability
-
Under-Isolation
- ❌ Don't share mutable state across goroutines without synchronization
- ✓ Use message passing or proper synchronization primitives
-
Ignoring Ordering Guarantees
- ❌ Don't assume message ordering when target language doesn't guarantee it
- ✓ Add explicit sequence numbers or use ordered channels if needed
Skill Template
When creating a new
convert-X-Y skill, use this template:
--- name: convert-<source>-<target> description: Convert <Source> code to <Target>. Use when migrating <Source> projects to <Target>, translating <Source> patterns to idiomatic <Target>, or refactoring <Source> codebases into <Target>. Extends meta-convert-dev with <Source>-to-<Target> specific patterns. --- # Convert <Source> to <Target> Convert <Source> code to idiomatic <Target>. This skill extends `meta-convert-dev` with <Source>-to-<Target> specific type mappings, idiom translations, and tooling. ## This Skill Extends - `meta-convert-dev` - Skill creation patterns - `meta-convert-guide` - Conversion methodology (APTV workflow, testing strategies) ## This Skill Adds - **Type mappings**: <Source> types → <Target> types - **Idiom translations**: <Source> patterns → idiomatic <Target> - **Error handling**: <Source> exceptions/errors → <Target> approach - **Async patterns**: <Source> async → <Target> async - **Tooling**: <Source>-to-<Target> specific tools ## Quick Reference | <Source> | <Target> | Notes | |----------|----------|-------| | `<type1>` | `<type1>` | ... | | `<type2>` | `<type2>` | ... | ## [Continue with 8 Pillar sections...]
References
Reference Materials (in references/
directory)
references/- platform-ecosystem.md - Platform families, runtime characteristics, FFI, stdlib mapping
- paradigm-translation.md - OOP↔FP, imperative↔declarative, type system shifts
- ownership-translation.md - GC↔ownership, borrowing patterns, lifetime translation
- numeric-edge-cases.md - Overflow, division semantics, float precision, arbitrary precision
- stdlib-mapping.md - Common stdlib function equivalents across languages
- build-system-mapping.md - Package managers and build tools
- migration-strategies.md - Migration approaches and strategies
- module-system-comparison.md - Import/export and visibility patterns
- naming-conventions.md - Naming conventions across languages
- performance-considerations.md - Performance patterns and considerations
Conversion Methodology
- APTV workflow, type system strategies, idiom patterns, testing approaches, common pitfallsmeta-convert-guide
Skills That Extend This Meta-Skill
These skills provide concrete, language-pair-specific implementations:
- TypeScript → Rust conversionconvert-typescript-rust
- TypeScript → Python conversionconvert-typescript-python
- TypeScript → Go conversionconvert-typescript-golang
- Go → Rust conversionconvert-golang-rust
- Python → Rust conversionconvert-python-rust
Related Meta-Skills
- Library development patternsmeta-library-dev
Language Skills
For language-specific fundamentals (not conversion):
- TypeScript development patternslang-typescript-dev
- Python development patternslang-python-dev
- Go development patternslang-golang-dev
- Rust development patternslang-rust-dev
Commands
- Create a new conversion skill using this meta-skill as foundation/create-lang-conversion-skill <source> <target>