# Convert Elixir Scala

> Bidirectional conversion between Elixir and Scala. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Elixir↔Scala specific patterns. Use when migrating Elixir/Phoenix applications to Scala, translating BEAM OTP patterns to Akka actors, or refactoring dynamic concurrency to statically-typed JVM patterns. Extends meta-convert-dev with Elixir-to-Scala specific patterns.

- Skill: `arustydev/convert-elixir-scala` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/convert-elixir-scala`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/convert-elixir-scala/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aRustyDev (https://skillmd.com/u/arustydev)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/arustydev/convert-elixir-scala

---


# Elixir ↔ Scala Conversion

Bidirectional conversion between Elixir and Scala. This skill extends `meta-convert-dev` with Elixir↔Scala specific type mappings, idiom translations, and tooling for translating between BEAM VM's dynamic actors and JVM's statically-typed Akka actors.

## 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**: Elixir types (dynamic) → Scala types (static)
- **Idiom translations**: Elixir patterns → idiomatic Scala with type safety
- **Runtime transition**: BEAM VM (lightweight processes) → JVM (Akka actors/Futures)
- **Concurrency models**: GenServer/Supervisor → Akka actors with typed protocols
- **OTP patterns**: Supervision trees → Akka supervision hierarchies
- **Data structures**: Elixir maps/structs → Scala case classes
- **Error handling**: {:ok/:error} tuples → Either/Try monads
- **Build tools**: Mix → sbt/Mill

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- Elixir language fundamentals - see `lang-elixir-dev`
- Scala language fundamentals - see `lang-scala-dev`
- Phoenix framework specifics - see `lang-scala-play-dev` for web frameworks

---

## Quick Reference

| Elixir | Scala | Notes |
|--------|-------|-------|
| `:atom` | Symbol / sealed trait | Atoms → sealed traits for exhaustiveness |
| `{:ok, value}` | `Right(value)` | Result tuples → Either/Try |
| `{:error, reason}` | `Left(reason)` | Error tuples → Either |
| `%{key: value}` | `Map("key" -> value)` | Maps require type parameters |
| `[1, 2, 3]` | `List(1, 2, 3)` | Lists immutable by default |
| `defmodule Module` | `object Module` | Module → singleton object |
| `def func(x)` | `def func(x: Type): ReturnType` | Functions require type signatures |
| `fn x -> x end` | `(x: Type) => x` | Anonymous functions |
| `\|>` (pipe) | `.map().flatMap()` | Pipe → method chaining |
| `GenServer` | `Actor[Protocol]` | Typed actors in Akka |
| `Supervisor` | `ActorSystem` / `Behaviors.supervise` | Supervision strategies |
| `Task.async` | `Future { }` | Async execution |
| Pattern matching | `match/case` | Similar but with static types |

---

## 8 Pillars Validation

### Elixir Coverage

| Pillar | Coverage | Status |
|--------|----------|--------|
| Module System | ✓ | defmodule, alias, import, use |
| Error Handling | ✓ | {:ok/:error} tuples, pattern matching, try/rescue |
| Concurrency Model | ✓ | Processes, GenServer, Supervisor, Task |
| Metaprogramming | ✓ | Macros, use directive, compile-time generation |
| Zero/Default Values | ✓ | nil, pattern matching with defaults |
| Serialization | ✓ | Phoenix params, Jason/Poison, Ecto |
| Build/Deps | ✓ | Mix, hex.pm, deps management |
| Testing Idioms | ✓ | ExUnit, doctests, Mox for mocking |

**Score: 8/8 (Green)**

### Scala Coverage

| Pillar | Coverage | Status |
|--------|----------|--------|
| Module System | ✓ | Packages, objects, imports, traits |
| Error Handling | ✓ | Either, Try, Option, exceptions |
| Concurrency Model | ✓ | Futures, Akka actors, Cats Effect IO |
| Metaprogramming | ✓ | Macros (Scala 2), inline/metaprogramming (Scala 3) |
| Zero/Default Values | ✓ | Option, default parameters, null (avoid) |
| Serialization | ✓ | Play JSON, Circe, uPickle, case classes |
| Build/Deps | ✓ | sbt, Mill, Maven Central |
| Testing Idioms | ✓ | ScalaTest, ScalaCheck, MUnit, specs2 |

**Score: 8/8 (Green)**

**Validation Status:** ✅ Both languages have comprehensive coverage. Conversion can proceed with confidence.

**Key Challenge:** Elixir's dynamic typing to Scala's static typing requires careful type inference and explicit type annotations.

---

## Type System Mapping

### Primitive Types

| Elixir | Scala | Notes |
|--------|-------|-------|
| `:atom` | `Symbol` / sealed trait | Use sealed traits for ADTs |
| `integer` | `Int` / `Long` / `BigInt` | Choose based on range needs |
| `float` | `Double` | 64-bit floating point |
| `true` / `false` | `Boolean` | Direct mapping |
| `nil` | `None` / `null` (avoid) | Prefer Option[T] |
| `"string"` | `String` | Immutable strings |
| `'charlist'` | `List[Char]` | Less common in Scala |

### Collection Types

| Elixir | Scala | Notes |
|--------|-------|-------|
| `[1, 2, 3]` | `List(1, 2, 3)` | Immutable linked lists |
| `{:ok, value}` | `Right(value): Either[Error, Value]` | Tagged tuples → Either |
| `{a, b, c}` | `(a, b, c): (A, B, C)` | Tuples map directly |
| `%{a: 1, b: 2}` | `Map("a" -> 1, "b" -> 2)` | Immutable maps |
| `MapSet.new([1, 2])` | `Set(1, 2)` | Immutable sets |
| Keyword list | `List[(Symbol, A)]` | Less common in Scala |
| Range `1..10` | `Range(1, 11)` | Inclusive → exclusive end |

### Composite Types

| Elixir | Scala | Notes |
|--------|-------|-------|
| `defstruct [:name, :age]` | `case class User(name: String, age: Int)` | Structs → case classes |
| `%User{name: "Alice"}` | `User("Alice", 30)` | Constructor syntax |
| `@type user :: %{name: String.t()}` | `type User = Map[String, Any]` | Type aliases |
| `@spec func(integer) :: String.t()` | `def func(x: Int): String` | Function signatures required |
| Protocol | Trait | Polymorphism via traits |

---

## Idiom Translation

### Pattern: Module Definition

**Elixir:**
```elixir
defmodule MyApp.Users do
  @moduledoc """
  Handles user-related operations.
  """

  alias MyApp.Repo
  import Ecto.Query

  @type user :: %{id: integer, name: String.t()}

  @spec get_user(integer) :: {:ok, user} | {:error, :not_found}
  def get_user(id) do
    case Repo.get(User, id) do
      nil -> {:error, :not_found}
      user -> {:ok, user}
    end
  end

  defp build_query(filters) do
    from u in User, where: ^filters
  end
end
```

**Scala:**
```scala
package myapp

/**
 * Handles user-related operations.
 */
object Users {
  import myapp.repo.Repo
  import myapp.models.User

  type UserId = Int
  type UserResult = Either[UserError, User]

  sealed trait UserError
  case object NotFound extends UserError

  def getUser(id: UserId): UserResult = {
    Repo.get[User](id) match {
      case Some(user) => Right(user)
      case None => Left(NotFound)
    }
  }

  private def buildQuery(filters: Map[String, Any]): Query = {
    Query.from[User].where(filters)
  }
}
```

**Why this translation:**
- `defmodule` → `object` (singleton)
- `@moduledoc` → Scaladoc `/** */`
- `@type` → `type` alias with explicit types
- `@spec` → method signature with types
- `{:ok/:error}` tuples → `Either[Error, Value]`
- Pattern matching on nil → `Option.match`
- CamelCase modules → package + object structure

---

### Pattern: Pattern Matching to Case Classes

**Elixir:**
```elixir
def process_response({:ok, %{"data" => data, "meta" => meta}}) do
  {:success, data, meta}
end

def process_response({:error, reason}) do
  {:failure, reason}
end

def handle_user(%User{name: name, age: age}) when age >= 18 do
  "Adult: #{name}"
end
```

**Scala:**
```scala
sealed trait Response
case class Success(data: String, meta: Map[String, Any]) extends Response
case class Error(reason: String) extends Response

def processResponse(response: Response): Result = response match {
  case Success(data, meta) =>
    SuccessResult(data, meta)
  case Error(reason) =>
    FailureResult(reason)
}

case class User(name: String, age: Int)

def handleUser(user: User): Option[String] = user match {
  case User(name, age) if age >= 18 =>
    Some(s"Adult: $name")
  case _ =>
    None
}
```

**Why this translation:**
- Multiple function clauses → sealed trait with case classes
- Guards → pattern guards with `if` in match
- Struct pattern matching → case class pattern matching
- Tag tuples → sealed trait hierarchy for type safety
- Exhaustiveness checking enforced by compiler

---

### Pattern: Pipelines to Method Chaining

**Elixir:**
```elixir
def process_data(input) do
  input
  |> String.trim()
  |> String.downcase()
  |> String.split(",")
  |> Enum.map(&String.trim/1)
  |> Enum.reject(&(&1 == ""))
  |> Enum.join(";")
end
```

**Scala:**
```scala
def processData(input: String): String = {
  input
    .trim
    .toLowerCase
    .split(",")
    .map(_.trim)
    .filterNot(_.isEmpty)
    .mkString(";")
}

// Or with explicit types for clarity
def processData(input: String): String = {
  val trimmed: String = input.trim
  val lowercased: String = trimmed.toLowerCase
  val parts: Array[String] = lowercased.split(",")
  val cleaned: Array[String] = parts.map(_.trim).filterNot(_.isEmpty)
  cleaned.mkString(";")
}
```

**Why this translation:**
- `|>` → method chaining (`.`)
- Enum functions → collection methods
- Capture operator `&func/arity` → anonymous functions `_`
- `Enum.reject` → `filterNot`
- `Enum.join` → `mkString`

---

### Pattern: GenServer to Akka Typed Actor

**Elixir:**
```elixir
defmodule UserCache do
  use GenServer

  # Client API

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end

  def put(key, value) do
    GenServer.cast(__MODULE__, {:put, key, value})
  end

  # Server Callbacks

  @impl true
  def init(_opts) do
    {:ok, %{}}
  end

  @impl true
  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  @impl true
  def handle_cast({:put, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end
end
```

**Scala (Akka Typed):**
```scala
import akka.actor.typed._
import akka.actor.typed.scaladsl.Behaviors

object UserCache {
  // Protocol
  sealed trait Command
  final case class Get(key: String, replyTo: ActorRef[Option[String]])
    extends Command
  final case class Put(key: String, value: String)
    extends Command

  // Behavior
  def apply(): Behavior[Command] = {
    behavior(Map.empty[String, String])
  }

  private def behavior(cache: Map[String, String]): Behavior[Command] = {
    Behaviors.receive { (context, message) =>
      message match {
        case Get(key, replyTo) =>
          replyTo ! cache.get(key)
          Behaviors.same

        case Put(key, value) =>
          behavior(cache + (key -> value))
      }
    }
  }
}

// Usage
val system: ActorSystem[UserCache.Command] =
  ActorSystem(UserCache(), "user-cache-system")

implicit val timeout: Timeout = 3.seconds
val futureResult: Future[Option[String]] =
  system.ask(ref => UserCache.Get("key", ref))
```

**Why this translation:**
- `GenServer` → Akka Typed `Actor[Protocol]`
- Message tuples → sealed trait protocol
- `handle_call` → synchronous message handling with `ask` pattern
- `handle_cast` → asynchronous message handling
- State recursion → immutable state updates
- Type safety enforced at compile time

---

### Pattern: Supervisor to Akka Supervision

**Elixir:**
```elixir
defmodule MyApp.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      {UserCache, []},
      {MyWorker, name: MyWorker, restart: :transient},
      {Registry, keys: :unique, name: MyApp.Registry}
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

**Scala (Akka Typed):**
```scala
import akka.actor.typed._
import akka.actor.typed.scaladsl._

object MyApp {
  sealed trait RootCommand

  def apply(): Behavior[RootCommand] = {
    Behaviors.setup { context =>
      // Spawn supervised children
      val userCache = context.spawn(
        UserCache(),
        "user-cache"
      )

      val worker = context.spawn(
        Behaviors.supervise(MyWorker())
          .onFailure[Exception](SupervisorStrategy.restart),
        "my-worker"
      )

      // Root behavior
      Behaviors.receiveMessage { message =>
        Behaviors.same
      }
    }
  }
}

// Starting the system
val system = ActorSystem(MyApp(), "my-app-system")
```

**Why this translation:**
- Supervision tree → `Behaviors.setup` with spawned children
- `:one_for_one` → individual supervision per actor
- `:transient` restart → `SupervisorStrategy.restart.withLimit`
- Named processes → named actors via `spawn`
- Declarative children list → imperative spawn calls

---

## Error Handling

### Elixir Error Model → Scala Error Model

**Elixir uses tagged tuples:**

```elixir
def divide(a, b) when b != 0, do: {:ok, a / b}
def divide(_, 0), do: {:error, :division_by_zero}

# Usage
case divide(10, 2) do
  {:ok, result} -> "Result: #{result}"
  {:error, reason} -> "Error: #{reason}"
end

# Or with pattern matching
{:ok, result} = divide(10, 2)
```

**Scala uses Either/Try monads:**

```scala
sealed trait MathError
case object DivisionByZero extends MathError

def divide(a: Double, b: Double): Either[MathError, Double] = {
  if (b != 0) Right(a / b)
  else Left(DivisionByZero)
}

// Usage with pattern matching
divide(10, 2) match {
  case Right(result) => s"Result: $result"
  case Left(error) => s"Error: $error"
}

// Or with for-comprehension
val result = for {
  x <- divide(10, 2)
  y <- divide(x, 5)
} yield y

// Or with Try for exceptions
import scala.util.{Try, Success, Failure}

def divideWithTry(a: Double, b: Double): Try[Double] = {
  Try(a / b)
}

divideWithTry(10, 0) match {
  case Success(value) => s"Result: $value"
  case Failure(exception) => s"Error: ${exception.getMessage}"
}
```

**Translation strategy:**
1. `{:ok, value}` → `Right(value): Either[Error, Value]`
2. `{:error, reason}` → `Left(reason): Either[Error, Value]`
3. Define error ADT with sealed trait
4. Use for-comprehensions for chaining operations
5. Consider `Try` for exception-based code

---

## Concurrency Patterns

### Elixir Async → Scala Async

**Elixir Task-based async:**

```elixir
def fetch_user_data(user_id) do
  task1 = Task.async(fn -> fetch_profile(user_id) end)
  task2 = Task.async(fn -> fetch_orders(user_id) end)
  task3 = Task.async(fn -> fetch_preferences(user_id) end)

  profile = Task.await(task1)
  orders = Task.await(task2)
  preferences = Task.await(task3)

  %{profile: profile, orders: orders, preferences: preferences}
end
```

**Scala Future-based async:**

```scala
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

def fetchUserData(userId: Int): Future[UserData] = {
  val profileF = Future { fetchProfile(userId) }
  val ordersF = Future { fetchOrders(userId) }
  val preferencesF = Future { fetchPreferences(userId) }

  for {
    profile <- profileF
    orders <- ordersF
    preferences <- preferencesF
  } yield UserData(profile, orders, preferences)
}

// Or with Applicative for parallel execution
import cats.implicits._

def fetchUserDataParallel(userId: Int): Future[UserData] = {
  (
    Future { fetchProfile(userId) },
    Future { fetchOrders(userId) },
    Future { fetchPreferences(userId) }
  ).mapN(UserData.apply)
}
```

**Why this translation:**
- `Task.async` → `Future { }`
- `Task.await` → `Await.result` (avoid) or for-comprehension
- Parallel execution → spawn all Futures immediately
- Sequential composition → for-comprehension
- Use Cats for applicative parallel composition

---

### Process Message Passing → Actor Tell/Ask

**Elixir:**

```elixir
defmodule Counter do
  def start do
    spawn(fn -> loop(0) end)
  end

  defp loop(count) do
    receive do
      {:increment, caller} ->
        send(caller, {:ok, count + 1})
        loop(count + 1)

      {:get, caller} ->
        send(caller, {:ok, count})
        loop(count)

      :stop ->
        :ok
    end
  end
end

# Usage
counter = Counter.start()
send(counter, {:increment, self()})
receive do
  {:ok, new_value} -> IO.puts("New value: #{new_value}")
end
```

**Scala (Akka Typed):**

```scala
object Counter {
  sealed trait Command
  case class Increment(replyTo: ActorRef[Response]) extends Command
  case class Get(replyTo: ActorRef[Response]) extends Command
  case object Stop extends Command

  sealed trait Response
  case class Value(count: Int) extends Response

  def apply(initialCount: Int): Behavior[Command] = {
    counter(initialCount)
  }

  private def counter(count: Int): Behavior[Command] = {
    Behaviors.receive { (context, message) =>
      message match {
        case Increment(replyTo) =>
          replyTo ! Value(count + 1)
          counter(count + 1)

        case Get(replyTo) =>
          replyTo ! Value(count)
          Behaviors.same

        case Stop =>
          Behaviors.stopped
      }
    }
  }
}

// Usage
val system = ActorSystem(Counter(0), "counter-system")
implicit val timeout: Timeout = 3.seconds

val futureCount: Future[Counter.Value] =
  system.ask(ref => Counter.Get(ref))
```

**Why this translation:**
- `spawn` → `ActorSystem` / `context.spawn`
- `send` → actor `!` (tell) operator
- `receive` → `Behaviors.receive` pattern matching
- Message tuples → sealed trait protocol
- Tail recursion → return new behavior

---

## Common Pitfalls

### 1. Dynamic Typing → Static Typing

**Problem:** Elixir allows dynamic typing; Scala requires explicit types.

```elixir
# Elixir - dynamic
def process(data) do
  case data do
    x when is_integer(x) -> x * 2
    x when is_binary(x) -> String.length(x)
    x when is_list(x) -> length(x)
  end
end
```

**Solution:** Use sealed trait ADT for type safety.

```scala
sealed trait Data
case class IntData(value: Int) extends Data
case class StringData(value: String) extends Data
case class ListData[A](values: List[A]) extends Data

def process(data: Data): Int = data match {
  case IntData(x) => x * 2
  case StringData(x) => x.length
  case ListData(xs) => xs.length
}
```

---

### 2. Pattern Match Exhaustiveness

**Problem:** Elixir warns on non-exhaustive matches; Scala errors.

```elixir
# Elixir - warning only
def handle({:ok, value}), do: value
# Missing {:error, _} case
```

**Solution:** Always handle all cases or use sealed traits.

```scala
sealed trait Result[+A]
case class Success[A](value: A) extends Result[A]
case class Failure(error: String) extends Result[Nothing]

def handle[A](result: Result[A]): A = result match {
  case Success(value) => value
  case Failure(error) => throw new Exception(error)
  // Compiler enforces exhaustiveness
}
```

---

### 3. Nil Safety

**Problem:** Elixir has `nil`; Scala should use `Option`.

```elixir
# Elixir
user = Repo.get(User, id)  # Can be nil
name = user.name           # Runtime error if nil
```

**Solution:** Use Option type.

```scala
val user: Option[User] = Repo.get[User](id)
val name: Option[String] = user.map(_.name)

// Or with for-comprehension
val name = for {
  u <- user
} yield u.name
```

---

### 4. Immutability by Convention → Enforced

**Problem:** Elixir immutability is by convention; Scala enforces it.

```elixir
# Elixir - creates new map
map = %{a: 1}
new_map = Map.put(map, :b, 2)
```

**Solution:** Use `val` and immutable collections.

```scala
val map = Map("a" -> 1)
val newMap = map + ("b" -> 2)  // Creates new map

// Avoid var and mutable collections
var mutableMap = scala.collection.mutable.Map("a" -> 1)  // Avoid
```

---

### 5. Process Supervision → Actor Supervision

**Problem:** Different restart semantics and error handling.

**Elixir:**
```elixir
# Let it crash - supervisor restarts
def handle_call(:dangerous, _from, state) do
  result = do_dangerous_operation()  # Might crash
  {:reply, result, state}
end
```

**Scala:**
```scala
// Must handle errors explicitly or define supervision strategy
Behaviors.supervise {
  Behaviors.receiveMessage {
    case Dangerous =>
      try {
        val result = doDangerousOperation()
        Behaviors.same
      } catch {
        case e: Exception =>
          throw e  // Supervisor handles via strategy
      }
  }
}.onFailure[Exception](SupervisorStrategy.restart)
```

---

## Tooling

| Category | Elixir | Scala | Notes |
|----------|--------|-------|-------|
| **Build Tool** | Mix | sbt, Mill | Mill is more modern |
| **Package Manager** | Hex | Maven Central | sbt resolves dependencies |
| **REPL** | iex | scala / ammonite | Ammonite more feature-rich |
| **Formatter** | mix format | scalafmt | Both auto-format |
| **Linter** | Credo | scalafix, Wartremover | Scala has multiple |
| **Testing** | ExUnit | ScalaTest, MUnit | Multiple test frameworks |
| **Property Testing** | StreamData | ScalaCheck | Similar concepts |
| **Actor System** | OTP | Akka | Akka Typed recommended |
| **Web Framework** | Phoenix | Play, Http4s, Akka HTTP | Play most similar |
| **DB** | Ecto | Slick, Doobie, Quill | Different approaches |
| **JSON** | Jason, Poison | Circe, Play JSON, uPickle | Macro-based derivation |
| **HTTP Client** | HTTPoison, Tesla | sttp, http4s-client | Functional HTTP clients |

---

## Examples

### Example 1: Simple - Data Transformation

**Before (Elixir):**
```elixir
defmodule DataProcessor do
  def transform_user(%{name: name, age: age}) do
    %{
      display_name: String.upcase(name),
      is_adult: age >= 18,
      category: categorize_age(age)
    }
  end

  defp categorize_age(age) when age < 13, do: :child
  defp categorize_age(age) when age < 20, do: :teenager
  defp categorize_age(_), do: :adult
end
```

**After (Scala):**
```scala
object DataProcessor {
  case class User(name: String, age: Int)

  sealed trait AgeCategory
  case object Child extends AgeCategory
  case object Teenager extends AgeCategory
  case object Adult extends AgeCategory

  case class TransformedUser(
    displayName: String,
    isAdult: Boolean,
    category: AgeCategory
  )

  def transformUser(user: User): TransformedUser = {
    TransformedUser(
      displayName = user.name.toUpperCase,
      isAdult = user.age >= 18,
      category = categorizeAge(user.age)
    )
  }

  private def categorizeAge(age: Int): AgeCategory = age match {
    case a if a < 13 => Child
    case a if a < 20 => Teenager
    case _ => Adult
  }
}
```

---

### Example 2: Medium - Error Handling Pipeline

**Before (Elixir):**
```elixir
defmodule UserRegistration do
  def register(params) do
    with {:ok, validated} <- validate_params(params),
         {:ok, user} <- create_user(validated),
         {:ok, _email} <- send_welcome_email(user) do
      {:ok, user}
    else
      {:error, :invalid_email} -> {:error, "Email is invalid"}
      {:error, :user_exists} -> {:error, "User already exists"}
      {:error, reason} -> {:error, "Registration failed: #{reason}"}
    end
  end

  defp validate_params(%{email: email, password: password})
    when byte_size(password) >= 8 do
    if valid_email?(email) do
      {:ok, %{email: email, password: password}}
    else
      {:error, :invalid_email}
    end
  end
  defp validate_params(_), do: {:error, :invalid_params}
end
```

**After (Scala):**
```scala
object UserRegistration {
  case class UserParams(email: String, password: String)
  case class User(id: Int, email: String)

  sealed trait RegistrationError
  case object InvalidEmail extends RegistrationError
  case object UserExists extends RegistrationError
  case class GeneralError(message: String) extends RegistrationError

  type RegistrationResult = Either[RegistrationError, User]

  def register(params: UserParams): RegistrationResult = {
    for {
      validated <- validateParams(params)
      user <- createUser(validated)
      _ <- sendWelcomeEmail(user)
    } yield user
  }

  private def validateParams(
    params: UserParams
  ): Either[RegistrationError, UserParams] = {
    if (params.password.length < 8) {
      Left(GeneralError("Password too short"))
    } else if (!validEmail(params.email)) {
      Left(InvalidEmail)
    } else {
      Right(params)
    }
  }

  private def createUser(
    params: UserParams
  ): Either[RegistrationError, User] = {
    // Database logic
    Right(User(1, params.email))
  }

  private def sendWelcomeEmail(
    user: User
  ): Either[RegistrationError, Unit] = {
    // Email logic
    Right(())
  }

  private def validEmail(email: String): Boolean = {
    email.contains("@")
  }
}
```

---

### Example 3: Complex - GenServer to Akka Actor with State

**Before (Elixir):**
```elixir
defmodule RateLimiter do
  use GenServer
  require Logger

  defstruct requests: %{}, window_ms: 60_000, max_requests: 100

  # Client API

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def check_rate(user_id) do
    GenServer.call(__MODULE__, {:check_rate, user_id})
  end

  def reset(user_id) do
    GenServer.cast(__MODULE__, {:reset, user_id})
  end

  # Server Callbacks

  @impl true
  def init(opts) do
    state = %__MODULE__{
      window_ms: Keyword.get(opts, :window_ms, 60_000),
      max_requests: Keyword.get(opts, :max_requests, 100)
    }
    {:ok, state}
  end

  @impl true
  def handle_call({:check_rate, user_id}, _from, state) do
    now = System.system_time(:millisecond)
    requests = Map.get(state.requests, user_id, [])

    # Filter out expired requests
    valid_requests =
      Enum.filter(requests, fn ts ->
        now - ts < state.window_ms
      end)

    count = length(valid_requests)

    if count < state.max_requests do
      new_requests = [now | valid_requests]
      new_state = put_in(state.requests[user_id], new_requests)
      {:reply, {:ok, state.max_requests - count - 1}, new_state}
    else
      {:reply, {:error, :rate_limit_exceeded}, state}
    end
  end

  @impl true
  def handle_cast({:reset, user_id}, state) do
    new_state = put_in(state.requests[user_id], [])
    {:noreply, new_state}
  end

  @impl true
  def handle_info(:cleanup, state) do
    now = System.system_time(:millisecond)

    new_requests =
      state.requests
      |> Enum.map(fn {user_id, requests} ->
        valid = Enum.filter(requests, fn ts ->
          now - ts < state.window_ms
        end)
        {user_id, valid}
      end)
      |> Enum.into(%{})

    Process.send_after(self(), :cleanup, 60_000)
    {:noreply, %{state | requests: new_requests}}
  end
end
```

**After (Scala with Akka Typed):**
```scala
import akka.actor.typed._
import akka.actor.typed.scaladsl._
import scala.concurrent.duration._

object RateLimiter {
  // Protocol
  sealed trait Command
  final case class CheckRate(
    userId: String,
    replyTo: ActorRef[RateResponse]
  ) extends Command
  final case class Reset(userId: String) extends Command
  private case object Cleanup extends Command

  // Responses
  sealed trait RateResponse
  final case class Allowed(remaining: Int) extends RateResponse
  case object RateLimitExceeded extends RateResponse

  // Configuration
  case class Config(
    windowMs: Long = 60000,
    maxRequests: Int = 100
  )

  // State
  private case class State(
    requests: Map[String, List[Long]],
    config: Config
  )

  def apply(config: Config = Config()): Behavior[Command] = {
    Behaviors.setup { context =>
      Behaviors.withTimers { timers =>
        timers.startTimerWithFixedDelay(Cleanup, 60.seconds)
        active(State(Map.empty, config), context)
      }
    }
  }

  private def active(
    state: State,
    context: ActorContext[Command]
  ): Behavior[Command] = {
    Behaviors.receiveMessage {
      case CheckRate(userId, replyTo) =>
        val now = System.currentTimeMillis()
        val requests = state.requests.getOrElse(userId, List.empty)

        // Filter expired requests
        val validRequests = requests.filter { ts =>
          now - ts < state.config.windowMs
        }

        val count = validRequests.length

        if (count < state.config.maxRequests) {
          val newRequests = now :: validRequests
          val newState = state.copy(
            requests = state.requests + (userId -> newRequests)
          )
          replyTo ! Allowed(state.config.maxRequests - count - 1)
          active(newState, context)
        } else {
          replyTo ! RateLimitExceeded
          Behaviors.same
        }

      case Reset(userId) =>
        val newState = state.copy(
          requests = state.requests - userId
        )
        active(newState, context)

      case Cleanup =>
        val now = System.currentTimeMillis()
        val cleaned = state.requests.map { case (userId, requests) =>
          val valid = requests.filter { ts =>
            now - ts < state.config.windowMs
          }
          userId -> valid
        }.filter(_._2.nonEmpty)

        active(state.copy(requests = cleaned), context)
    }
  }
}

// Usage
val system: ActorSystem[RateLimiter.Command] =
  ActorSystem(RateLimiter(RateLimiter.Config()), "rate-limiter")

implicit val timeout: Timeout = 3.seconds
val futureResponse: Future[RateLimiter.RateResponse] =
  system.ask(ref => RateLimiter.CheckRate("user123", ref))
```

---

## See Also

For more examples and patterns, see:

- `meta-convert-dev` - Foundational patterns with cross-language examples
- `lang-elixir-dev` - Elixir development patterns
- `lang-scala-dev` - Scala development patterns
- `lang-scala-akka-dev` - Advanced Akka patterns

Cross-cutting pattern skills:

- `patterns-concurrency-dev` - Async, actors, processes across languages
- `patterns-serialization-dev` - JSON, validation across languages
- `patterns-metaprogramming-dev` - Macros, code generation across languages

