Agent Skills: Roc ↔ Scala Conversion

Bidirectional conversion between Roc and Scala. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Roc↔Scala specific patterns. Use when migrating Roc projects to JVM/Scala, translating pure functional patterns to object-functional hybrid, or refactoring Roc codebases. Extends meta-convert-dev with Roc-to-Scala specific patterns.

UncategorizedID: arustydev/ai/convert-roc-scala

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-roc-scala

Skill Files

Browse the full folder contents for convert-roc-scala.

Download Skill

Loading file tree…

content/skills/convert-roc-scala/SKILL.md

Skill Metadata

Name
convert-roc-scala
Description
Bidirectional conversion between Roc and Scala. Use when migrating projects between these languages in either direction. Extends meta-convert-dev with Roc↔Scala specific patterns. Use when migrating Roc projects to JVM/Scala, translating pure functional patterns to object-functional hybrid, or refactoring Roc codebases. Extends meta-convert-dev with Roc-to-Scala specific patterns.

Roc ↔ Scala Conversion

Bidirectional conversion between Roc and Scala. This skill extends meta-convert-dev with Roc↔Scala specific type mappings, idiom translations, and architectural patterns for moving from platform-based pure functional programming to JVM-based object-functional hybrid programming.

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: Roc static types → Scala JVM types
  • Paradigm translation: Pure functional with platform separation → Object-functional hybrid
  • Idiom translations: Roc functional patterns → Scala patterns
  • Error handling: Result types → Either/Try/Exceptions
  • Concurrency: Platform Tasks → Futures/Actors
  • Module system: Roc platform/application architecture → Scala packages/objects
  • Type classes: Roc abilities → Scala implicits/type classes

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Roc language fundamentals - see lang-roc-dev
  • Scala language fundamentals - see lang-scala-dev

Quick Reference

| Roc | Scala | Notes | |-----|-------|-------| | I64 / I32 | Long / Int | Specify bit width becomes JVM types | | F64 | Double | 64-bit float | | Bool | Boolean | Direct mapping | | Str | String | UTF-8 → UTF-16 (JVM) | | [Some A, None] | Option[A] | Tag union → built-in type | | Result R L | Either[L, R] | Note: order matches | | List A | List[A] or Vector[A] | Immutable lists | | Set A | Set[A] | Unique values | | Dict K V | Map[K, V] | Key-value map | | Record { } | case class | Records → case classes | | Tag union [] | sealed trait | Sum types | | Ability | trait (type class) | Type class pattern | | Task A err | Future[A] | Platform-provided → JVM concurrency | | {} | Unit | Empty record |

When Converting Code

  1. Analyze platform boundaries before writing Scala
  2. Identify effect implementations - convert platform capabilities to libraries
  3. Map data structures to objects - records become case classes, consider state encapsulation
  4. Design for mutability options - Roc's pure transformations can become Scala vars if needed
  5. Choose concurrency model - Tasks become Futures, Akka actors, or effect systems
  6. Test equivalence - verify behavior matches despite runtime differences

Paradigm Translation

Mental Model Shift: Pure Functional + Platform → Object-Functional Hybrid

| Roc Concept | Scala Approach | Key Insight | |-------------|----------------|-------------| | Record + functions | Case class with methods or separate functions | Can choose data+methods or data+functions | | Tag unions | Sealed traits + case classes | Similar concept, class hierarchy | | Pure transformation | val or var | Can choose immutability or mutation | | Module-level functions | Object with methods | Companion objects as modules | | Ability constraint | Implicit parameter or type class | Similar type class pattern | | Platform Task | Future, IO, ZIO | Choose effect system | | Platform capability | Library import | What was platform becomes library | | Module scope | Package object or companion | Namespace organization | | Record composition | Trait mixing or composition | Multiple composition strategies |

Functional Paradigm Alignment

| Roc Pattern | Scala Pattern | Conceptual Translation | |-------------|---------------|------------------------| | Pipeline \|> | Method chaining or andThen | Reversed order possible | | when expression | Pattern matching match | Very similar syntax | | Record type | Case class | Nominal types with structural similarity | | Tag union | Sealed trait ADT | Direct correspondence | | No conversion | Explicit conversion or implicits | Conversion becomes explicit or automatic | | Function types | Function types with sugar | Direct support with different syntax |


Type System Mapping

Primitive Types

| Roc | Scala | Notes | |-----|-------|-------| | I8 | Byte | 8-bit signed | | I16 | Short | 16-bit signed | | I32 | Int | 32-bit signed (common) | | I64 | Long | 64-bit signed | | F32 | Float | 32-bit float | | F64 | Double | 64-bit float | | Bool | Boolean | Direct mapping | | U32 | Int (careful with sign) | No unsigned in Scala 2, consider Long | | Str | String | UTF-8 → UTF-16 | | {} | Unit | Empty record | | - | Any | No direct equivalent in Roc | | - | Nothing | No direct equivalent in Roc |

Collection Types

| Roc | Scala | Notes | |-----|-------|-------| | List A | List[A] | Immutable, efficient prepend | | List A | Vector[A] | For random access, use Vector | | Set A | Set[A] | Unique values | | Dict K V | Map[K, V] | Key-value mapping | | Generator pattern | LazyList[A] or Iterator[A] | Lazy evaluation | | [Some A, None] | Option[A] | Optional values | | Result R L | Either[L, R] | Order matches |

Composite Types

| Roc | Scala | Notes | |-----|-------|-------| | User : { name : Str, ... } | case class User(name: String, ...) | Records → case classes | | Color : [Red, Green, Blue] | sealed trait Color; case object Red extends Color... | Sum types | | Ability or module | trait or object | Depends on use case | | interface Utils | object Utils | Module → singleton | | (A, B) | (A, B) | Tuples map directly | | Type parameters a | Generics [A] | Similar concept |

Function Types

| Roc | Scala | Notes | |-----|-------|-------| | {} -> R | () => R | Zero-arg function | | A -> R | A => R | Single arg | | A, B -> R | (A, B) => R | Multiple args (tuple) | | A -> (B -> C) | A => B => C | Curried functions |


Idiom Translation

Pattern 1: Simple Function and Record

Roc:

interface User
    exposes [User, create, greet]
    imports []

User : {
    name : Str,
    age : U32,
    email : Str,
}

create : Str, U32, Str -> User
create = \name, age, email ->
    { name, age, email }

greet : User -> Str
greet = \{ name, age } ->
    "Hello, \(name)! You are \(Num.toStr(age)) years old."

Scala:

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

object User {
  def create(name: String, age: Int, email: String): User = {
    User(name, age, email)
  }

  def greet(user: User): String = {
    s"Hello, ${user.name}! You are ${user.age} years old."
  }
}

Why this translation:

  • Roc record → Scala case class
  • Roc interface (module) → Scala companion object
  • String interpolation syntax differs
  • Type inference works in both but Scala encourages explicit return types

Pattern 2: Tag Union with Pattern Matching

Roc:

Result a : [Success a, Failure Str, Pending]

handle : Result a -> Str where a implements Inspect
handle = \result ->
    when result is
        Success(value) -> "Got: \(Inspect.toStr(value))"
        Failure(error) -> "Error: \(error)"
        Pending -> "Waiting..."

Scala:

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

def handle[A](result: Result[A]): String = result match {
  case Success(value) => s"Got: $value"
  case Failure(error) => s"Error: $error"
  case Pending => "Waiting..."
}

Why this translation:

  • Tag union → Sealed trait + case classes/objects
  • whenmatch
  • Ability constraint → type parameter (toString implicit)
  • Exhaustiveness checking works in both

Pattern 3: Option Handling

Roc:

findUser : U64, List User -> [Some User, None]
findUser = \id, users ->
    users
    |> List.findFirst(\user -> user.id == id)
    |> Result.map(Some)
    |> Result.withDefault(None)

getEmail : [Some User, None] -> Str
getEmail = \maybeUser ->
    when maybeUser is
        Some({ email }) -> email
        None -> "no email"

# Nested when for comprehension-like flow
combineUsers : U64, U64, List User -> [Some (User, User), None]
combineUsers = \id1, id2, users ->
    when findUser(id1, users) is
        Some(user1) ->
            when findUser(id2, users) is
                Some(user2) -> Some((user1, user2))
                None -> None
        None -> None

Scala:

def findUser(id: Long, users: List[User]): Option[User] = {
  users.find(_.id == id)
}

def getEmail(maybeUser: Option[User]): String = {
  maybeUser.map(_.email).getOrElse("no email")
}

// For-comprehension
def combineUsers(id1: Long, id2: Long, users: List[User]): Option[(User, User)] = {
  for {
    user1 <- findUser(id1, users)
    user2 <- findUser(id2, users)
  } yield (user1, user2)
}

Why this translation:

  • Roc tag union [Some a, None] → Scala Option[A]
  • Nested when → for-comprehension (more ergonomic)
  • Method chaining map/getOrElse → similar in Scala
  • Scala's Option is more idiomatic and has richer API

Pattern 4: List Processing

Roc:

numbers = [1, 2, 3, 4, 5]

doubled = List.map(numbers, \n -> n * 2)
evens = List.keepIf(numbers, \n -> n % 2 == 0)
sum = List.walk(numbers, 0, Num.add)

# List comprehension becomes pipeline
squares = numbers
    |> List.keepIf(\x -> x % 2 == 0)
    |> List.map(\x -> x * x)

Scala:

val numbers = List(1, 2, 3, 4, 5)

val doubled = numbers.map(n => n * 2)
val evens = numbers.filter(n => n % 2 == 0)
val sum = numbers.foldLeft(0)(_ + _)

// For-comprehension
val squares = for {
  x <- numbers
  if x % 2 == 0
} yield x * x

// Or method chaining
val squares2 = numbers.filter(_ % 2 == 0).map(x => x * x)

Why this translation:

  • List.map.map (method syntax)
  • List.keepIf.filter
  • List.walk.foldLeft
  • Pipeline → method chaining or for-comprehension
  • Scala offers multiple equivalent styles

Pattern 5: Error Handling with Result

Roc:

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

calculate : I64, I64, I64 -> Result I64 [DivByZero]
calculate = \a, b, c ->
    x = divide!(a, b)
    y = divide!(x, c)
    Ok(y)

# Try operator for early returns
parseInt : Str -> Result I64 [ParseError]
parseInt = \s ->
    when Str.toI64(s) is
        Ok(n) -> Ok(n)
        Err(_) -> Err(ParseError)

safeParse : Str -> [Some I64, None]
safeParse = \s ->
    when parseInt(s) is
        Ok(n) -> Some(n)
        Err(_) -> None

Scala:

def divide(a: Long, b: Long): Either[String, Long] = {
  if (b == 0) Left("DivByZero")
  else Right(a / b)
}

def calculate(a: Long, b: Long, c: Long): Either[String, Long] = {
  for {
    x <- divide(a, b)
    y <- divide(x, c)
  } yield y
}

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

def parseInt(s: String): Try[Long] = Try(s.toLong)

def safeParse(s: String): Option[Long] = parseInt(s).toOption

Why this translation:

  • Result ok errEither[err, ok] (same order)
  • Try operator ! → for-comprehension for early returns
  • Roc's explicit error tags → Scala's String or custom types
  • Try captures exceptions similar to Roc's result pattern
  • .toOption converts Result/Either/Try to Option

Pattern 6: Abilities to Type Classes

Roc:

# Roc abilities are automatic for basic types
toString : a -> Str where a implements Inspect
toString = \value ->
    Inspect.toStr(value)

# Usage - Inspect is automatically implemented
expect toString(42) == "42"
expect toString("hello") == "\"hello\""

# For custom types, abilities are derived automatically
User : { name : Str, age : U32 }
user = { name: "Alice", age: 30 }

expect Inspect.toStr(user) == "{ name: \"Alice\", age: 30 }"

Scala:

// Type class definition
trait Show[A] {
  def show(a: A): String
}

object Show {
  implicit val intShow: Show[Int] = (a: Int) => a.toString
  implicit val stringShow: Show[String] = (a: String) => s"'$a'"
}

def toString[A](value: A)(implicit s: Show[A]): String = {
  s.show(value)
}

// Usage
toString(42)      // Uses intShow
toString("hello") // Uses stringShow

// For custom types, define implicit manually
case class User(name: String, age: Int)

implicit val userShow: Show[User] = (u: User) =>
  s"""{ name: "${u.name}", age: ${u.age} }"""

toString(User("Alice", 30))

Why this translation:

  • Roc ability → Scala trait (type class)
  • Automatic derivation → manual implicit instances
  • Ability constraint → implicit parameter
  • Roc has fewer abilities but they're automatic
  • Scala requires explicit instances but more flexible

Pattern 7: Higher-Order Functions and Currying

Roc:

applyTwice : (a -> a), a -> a
applyTwice = \f, x ->
    f(f(x))

# Currying in Roc requires explicit function return
add : I64 -> (I64 -> I64)
add = \a ->
    \b -> a + b

add5 = add(5)

compose : (b -> c), (a -> b) -> (a -> c)
compose = \f, g ->
    \a -> f(g(a))

Scala:

def applyTwice[A](f: A => A, x: A): A = f(f(x))

// Currying is natural in Scala
def add(a: Long)(b: Long): Long = a + b
val add5 = add(5) _

def compose[A, B, C](f: B => C, g: A => B): A => C = {
  a => f(g(a))
}

// Or use built-in compose/andThen
val f: Int => Int = _ * 2
val g: Int => Int = _ + 1
val composed = f compose g  // f(g(x))
val andThen = f andThen g   // g(f(x))

Why this translation:

  • Higher-order functions work similarly
  • Currying is more natural in Scala (multiple parameter lists)
  • Scala has built-in compose and andThen on functions
  • Type signatures use => vs ->

Pattern 8: Records with Update

Roc:

Config : {
    host : Str,
    port : U16,
    timeout : U32,
    retries : U32,
}

defaultConfig : Str, U16 -> Config
defaultConfig = \host, port ->
    {
        host,
        port,
        timeout: 5000,
        retries: 3,
    }

config = defaultConfig("localhost", 8080)
updated = { config &
    port: 9090,
    retries: 5,
}

Scala:

case class Config(
  host: String,
  port: Int,
  timeout: Int = 5000,
  retries: Int = 3
)

val config = Config("localhost", 8080)
val updated = config.copy(port = 9090, retries = 5)

Why this translation:

  • Roc record update { record & ... } → Scala copy method
  • Default values can be in constructor directly
  • Case class provides copy automatically
  • Scala's syntax is more concise

Concurrency Patterns

Roc Task vs Scala Future

Roc delegates all concurrency to the platform. Scala uses JVM threads and Futures.

Roc:

import pf.Task exposing [Task]
import pf.Database

# Platform provides Task type
fetchUser : U64 -> Task User [DbErr]
fetchUser = \id ->
    Database.query!("SELECT * FROM users WHERE id = \(Num.toStr(id))")

fetchPosts : U64 -> Task (List Post) [DbErr]
fetchPosts = \userId ->
    Database.query!("SELECT * FROM posts WHERE author = \(Num.toStr(userId))")

# Sequential composition using !
result : Task (User, List Post) [DbErr]
result =
    user = fetchUser!(123)
    posts = fetchPosts!(user.id)
    Task.ok((user, posts))

# Platform provides parallel primitives
users : Task (List User) [DbErr]
users =
    Task.sequence([
        fetchUser(1),
        fetchUser(2),
        fetchUser(3),
    ])

Scala:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

def fetchUser(id: Long): Future[User] = Future {
  // Database operation
  database.query(s"SELECT * FROM users WHERE id = $id")
}

def fetchPosts(userId: Long): Future[List[Post]] = Future {
  database.query(s"SELECT * FROM posts WHERE author = $userId")
}

// Composition with for-comprehension
val result: Future[(User, List[Post])] = for {
  user <- fetchUser(123)
  posts <- fetchPosts(user.id)
} yield (user, posts)

// Parallel execution
val users: Future[List[User]] = Future.sequence(
  List(1, 2, 3).map(fetchUser)
)

Why this translation:

  • Roc platform Task → Scala Future
  • Try operator ! → for-comprehension
  • Platform handles concurrency → ExecutionContext manages threads
  • Task.sequence → Future.sequence
  • Roc apps stay pure, Scala code manages effects explicitly

Roc Platform vs Scala Akka Actors

Roc:

# Roc has no built-in actors
# Design as pure state machine

State : I64

init : State
init = 0

increment : State -> State
increment = \count ->
    count + 1

getCount : State -> I64
getCount = \count ->
    count

# Platform would provide state management if needed
# Application code remains pure

Scala (Akka Typed):

import akka.actor.typed._
import akka.actor.typed.scaladsl.Behaviors

sealed trait CounterMsg
case object Increment extends CounterMsg
case class GetCount(replyTo: ActorRef[Int]) extends CounterMsg

def counter(count: Int): Behavior[CounterMsg] =
  Behaviors.receive { (context, message) =>
    message match {
      case Increment =>
        counter(count + 1)
      case GetCount(replyTo) =>
        replyTo ! count
        Behaviors.same
    }
  }

// Create actor system
val system = ActorSystem(counter(0), "counter-system")

Why this translation:

  • Roc pure state functions → Akka actor behaviors
  • Platform handles concurrency → Actor system handles messages
  • Roc's pure approach → Akka's message-passing model
  • State transformations → State transitions in actors

Module System Translation

Roc Interface → Scala Package/Object

Roc:

interface UserService
    exposes [User, create, validate]
    imports []

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

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

validate : User -> Result User [InvalidEmail]
validate = \user ->
    if Str.contains(user.email, "@") then
        Ok(user)
    else
        Err(InvalidEmail)

# Private helper (not in exposes)
generateId : {} -> U64
generateId = \{} ->
    # Implementation
    123

Scala:

package com.example.users

case class User(id: Long, name: String, email: String)

object UserService {
  def create(name: String, email: String): User = {
    val id = generateId()
    User(id, name, email)
  }

  def validate(user: User): Either[String, User] = {
    if (user.email.contains("@")) Right(user)
    else Left("Invalid email")
  }

  // Private helper
  private def generateId(): Long = {
    // Implementation
    123L
  }
}

Why this translation:

  • Roc interface → Scala package + companion object
  • Explicit exposes → Scala visibility modifiers
  • Not in exposes → private methods
  • Public API → public methods

Common Pitfalls

1. Assuming Immutability is Enforced

Roc Guarantee:

# No mutable state - always returns new value
increment : I64 -> I64
increment = \counter ->
    counter + 1

Scala Allows Mutation:

// Can use var if needed
var counter = 0
counter += 1  // Mutation is allowed

// But prefer immutable val
val counter = 0
val newCounter = counter + 1

Why: Scala allows both val (immutable) and var (mutable). Choose wisely.

2. Expecting Platform Separation

Pitfall: Trying to keep platform separation in Scala.

Roc Approach:

# Platform provides effects
import pf.File
import pf.Task exposing [Task]

readFile : Str -> Task Str [FileErr]
readFile = \path ->
    File.readUtf8(path)

Scala Reality:

// Direct I/O in application code
import scala.io.Source

def readFile(path: String): String = {
  Source.fromFile(path).mkString
}

// Or wrap in effect system if desired (Cats Effect, ZIO)
import cats.effect.IO

def readFile(path: String): IO[String] = {
  IO(Source.fromFile(path).mkString)
}

Why: Scala doesn't enforce platform separation. Effects can be anywhere.

3. Tag Union vs Sealed Trait Verbosity

Pitfall: Expecting concise tag union syntax.

Roc:

Result a : [Success a, Failure Str, Pending]

Scala:

// More verbose but more powerful
sealed trait Result[+A]
case class Success[A](value: A) extends Result[A]
case class Failure(error: String) extends Result[Nothing]
case object Pending extends Result[Nothing]

Why: Scala requires explicit class definitions but allows more flexibility.

4. Ability Derivation is Not Automatic

Pitfall: Expecting automatic type class instances.

Roc:

# Abilities derived automatically for records
User : { name : Str, age : U32 }
# Automatically has Eq, Hash, Inspect, etc.

Scala:

// Must derive explicitly or define instances
case class User(name: String, age: Int)

// Ordering must be explicit
implicit val userOrdering: Ordering[User] = Ordering.by(_.name)

// Or use libraries like cats for derivation
import cats.derived._
implicit val userShow: Show[User] = derived.semiauto.show

Why: Scala doesn't auto-derive type class instances. Must be explicit.

5. No Null in Roc, Null Exists in Scala

Roc:

# No null! Always use tag unions
maybeUser : [Some User, None]
maybeUser = None

Scala:

// Null exists but should be avoided
var user: User = null  // Bad!

// Use Option instead
val maybeUser: Option[User] = None  // Good

Why: Scala has null for Java interop. Always use Option to avoid NPEs.

6. Different Error Handling Philosophies

Pitfall: Expecting all errors as Result types.

Roc:

# All errors are explicit in Result
divide : I64, I64 -> Result I64 [DivByZero]

Scala:

// Can use exceptions, Either, or Try
def divide(a: Int, b: Int): Int = {
  if (b == 0) throw new ArithmeticException("Division by zero")
  else a / b
}

// Or typed errors
def divideSafe(a: Int, b: Int): Either[String, Int] = {
  if (b == 0) Left("Division by zero")
  else Right(a / b)
}

Why: Scala allows exceptions. Choose error handling strategy based on context.


Tooling

| Purpose | Roc | Scala | Notes | |---------|-----|-------|-------| | Build tool | roc CLI | sbt, Mill, Maven | Scala has multiple build tools | | Package manager | Platform dependencies | sbt, Maven Central | Scala uses JVM ecosystem | | Testing | roc test | ScalaTest, MUnit, specs2 | Multiple testing frameworks | | REPL | roc repl | scala REPL | Interactive exploration | | Formatter | roc format | Scalafmt | Configurable formatting | | Type checking | roc check | scalac | Scala compiler checks types | | Documentation | Comments | Scaladoc | Generate API docs |


Examples

Example 1: Simple HTTP Client

Roc (basic-cli platform):

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

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

fetchUrl : Str -> Task Str [HttpErr]
fetchUrl = \url ->
    response = Http.get!(url)
    Task.ok(response.body)

main : Task {} []
main =
    content = fetchUrl!("https://example.com")
    Stdout.line!(content)

Scala (Akka HTTP):

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import scala.concurrent.Future
import scala.concurrent.duration._

implicit val system = ActorSystem()
import system.dispatcher

def fetchUrl(url: String): Future[String] = {
  Http().singleRequest(HttpRequest(uri = url)).flatMap { response =>
    response.entity.toStrict(5.seconds).map(_.data.utf8String)
  }
}

val content: Future[String] = fetchUrl("https://example.com")
content.foreach(println)

Example 2: Data Processing Pipeline

Roc:

User : { id : U64, name : Str, age : U32, active : Bool }

users = [
    { id: 1, name: "Alice", age: 30, active: Bool.true },
    { id: 2, name: "Bob", age: 25, active: Bool.false },
    { id: 3, name: "Charlie", age: 35, active: Bool.true },
]

activeUserNames = users
    |> List.keepIf(\user -> user.active)
    |> List.keepIf(\user -> user.age >= 30)
    |> List.map(\user -> user.name)
    |> List.sortAsc

# Result: ["Alice", "Charlie"]

Scala:

case class User(id: Int, name: String, age: Int, active: Boolean)

val users = List(
  User(1, "Alice", 30, true),
  User(2, "Bob", 25, false),
  User(3, "Charlie", 35, true)
)

val activeUserNames = users
  .filter(_.active)
  .filter(_.age >= 30)
  .map(_.name)
  .sorted

// Result: List("Alice", "Charlie")

Example 3: Error Handling Pipeline

Roc:

parseAndDivide : Str, Str -> Result I64 [InvalidA, InvalidB, DivByZero]
parseAndDivide = \aStr, bStr ->
    a =
        when Str.toI64(aStr) is
            Ok(n) -> Ok(n)
            Err(_) -> Err(InvalidA)

    b =
        when Str.toI64(bStr) is
            Ok(n) -> Ok(n)
            Err(_) -> Err(InvalidB)

    # Using try operator for early returns
    aVal = a!
    bVal = b!

    if bVal == 0 then
        Err(DivByZero)
    else
        Ok(aVal // bVal)

expect parseAndDivide("10", "2") == Ok(5)
expect parseAndDivide("10", "0") == Err(DivByZero)
expect parseAndDivide("abc", "2") == Err(InvalidA)

Scala:

def parseAndDivide(aStr: String, bStr: String): Either[String, Int] = {
  for {
    a <- aStr.toIntOption.toRight(s"Invalid a: $aStr")
    b <- bStr.toIntOption.toRight(s"Invalid b: $bStr")
    result <- if (b != 0) Right(a / b) else Left("Division by zero")
  } yield result
}

parseAndDivide("10", "2")  // Right(5)
parseAndDivide("10", "0")  // Left("Division by zero")
parseAndDivide("abc", "2") // Left("Invalid a: abc")

Performance Considerations

Roc vs Scala Performance Differences

| Aspect | Roc | Scala | Impact | |--------|-----|-------|--------| | Runtime | Native compilation | JVM (GC, JIT) | Roc generally faster startup, Scala has longer warmup | | Collections | Native data structures | JVM optimized | Different performance characteristics | | Concurrency | Platform-managed | Thread pool, async | Platform vs runtime dependent | | Memory | Platform-managed | Heap-based, GC | Scala has GC pauses | | Startup | Instant | JVM warmup time | Roc has immediate performance |

Optimization Tips

  1. Leverage JVM optimizations - JIT compiler optimizes hot paths over time
  2. Choose appropriate collections - Vector for random access, List for sequential
  3. Use immutable collections - Better GC characteristics with structural sharing
  4. Consider lazy evaluation - LazyList or Iterator for large datasets
  5. Profile JVM performance - Different bottlenecks than native code

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational patterns with cross-language examples
  • lang-roc-dev - Roc development patterns
  • lang-scala-dev - Scala development patterns
  • convert-clojure-scala - Similar functional language conversion (Clojure → Scala)

Cross-cutting pattern skills:

  • patterns-concurrency-dev - Tasks vs Futures/Actors vs other approaches
  • patterns-serialization-dev - JSON, validation across languages
  • patterns-metaprogramming-dev - Abilities vs implicits vs other approaches