convert-roc-scala
Convert Roc code to idiomatic Scala. 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.
When & Why to Use This Skill
This Claude skill facilitates the seamless migration of Roc projects to the JVM/Scala ecosystem by converting Roc code into idiomatic Scala. It provides comprehensive mappings for types, functional paradigms, and architectural patterns, ensuring that pure functional logic is correctly translated into Scala's object-functional hybrid style while maintaining code quality and performance.
Use Cases
- Migrating Roc-based microservices to the JVM to leverage Scala's extensive library ecosystem and enterprise-grade infrastructure.
- Refactoring Roc codebases into Scala while preserving functional integrity through precise mapping of Tag Unions to Sealed Traits and Case Classes.
- Translating Roc's platform-specific concurrency models (Tasks) into Scala's Future-based or Akka Actor-based asynchronous patterns.
- Converting Roc 'Abilities' into Scala type classes and implicits to maintain polymorphic behavior and type safety across the migration.
| name | convert-roc-scala |
|---|---|
| description | Convert Roc code to idiomatic Scala. 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. |
Convert Roc to Scala
Convert Roc code to idiomatic Scala. This skill extends meta-convert-dev with Roc-to-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 - Reverse conversion (Scala → Roc) - see
convert-scala-roc
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
- Analyze platform boundaries before writing Scala
- Identify effect implementations - convert platform capabilities to libraries
- Map data structures to objects - records become case classes, consider state encapsulation
- Design for mutability options - Roc's pure transformations can become Scala vars if needed
- Choose concurrency model - Tasks become Futures, Akka actors, or effect systems
- 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
when→match- 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]→ ScalaOption[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→.filterList.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 err→Either[err, ok](same order)- Try operator
!→ for-comprehension for early returns - Roc's explicit error tags → Scala's String or custom types
Trycaptures exceptions similar to Roc's result pattern.toOptionconverts 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
composeandandThenon 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 & ... }→ Scalacopymethod - Default values can be in constructor directly
- Case class provides
copyautomatically - 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 →
privatemethods - 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
- Leverage JVM optimizations - JIT compiler optimizes hot paths over time
- Choose appropriate collections - Vector for random access, List for sequential
- Use immutable collections - Better GC characteristics with structural sharing
- Consider lazy evaluation - LazyList or Iterator for large datasets
- Profile JVM performance - Different bottlenecks than native code
See Also
For more examples and patterns, see:
meta-convert-dev- Foundational patterns with cross-language exampleslang-roc-dev- Roc development patternslang-scala-dev- Scala development patternsconvert-clojure-scala- Similar functional language conversion (Clojure → Scala)
Cross-cutting pattern skills:
patterns-concurrency-dev- Tasks vs Futures/Actors vs other approachespatterns-serialization-dev- JSON, validation across languagespatterns-metaprogramming-dev- Abilities vs implicits vs other approaches