| 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