xiaohongshu
xiaohongshu
convert-scala-roc
Convert Scala code to idiomatic Roc. Use when migrating Scala projects to Roc, translating JVM/FP patterns to pure functional patterns, or refactoring Scala codebases. Extends meta-convert-dev with Scala-to-Roc specific patterns.
Full skill instructions
Convert Scala code to idiomatic Roc. This skill extends meta-convert-dev with Scala-to-Roc specific type mappings, idiom translations, and architectural patterns for moving from JVM-based functional programming to platform-based pure functional programming.
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.
meta-convert-devlang-scala-devlang-roc-devconvert-roc-scala| Scala | Roc | Notes |
|---|---|---|
Int | I64 / I32 | Specify bit width |
Long | I64 | 64-bit signed |
Double | F64 | 64-bit float |
Boolean | Bool | Direct mapping |
String | Str | UTF-8 strings |
Option[A] | [Some A, None] | Tag union |
Either[L, R] | Result R L | Note: order swapped |
Try[A] | Result A [Err Str] | Exception → error tag |
List[A] | List A | Immutable list |
Vector[A] | List A | Roc List is efficient |
Set[A] | Set A | Unique values |
Map[K, V] | Dict K V | Key-value map |
case class | Record { } | Structural records |
sealed trait | Tag union [] | Sum types |
trait (interface) | Ability | Type class pattern |
Future[A] | Task A err | Platform-provided |
Unit | {} | Empty record |
| Scala Concept | Roc Approach | Key Insight |
|---|---|---|
| Class with state | Record + functions operating on record | Data and behavior separated, no hidden state |
| Inheritance | Composition with records | Favor records and tag unions over class hierarchies |
| var (mutation) | New value creation | Explicit transformation, not mutation |
| Companion object | Module with functions | Namespace for related functions |
| Implicit parameter | Ability constraint | Type class pattern via abilities |
| Future | Platform Task | Effects are platform capability |
| Actor | Platform concern | Concurrency handled by host |
| Singleton object | Module-level constants | Global state avoided, use module scope |
| Trait mixing | Record composition | Combine records, not behaviors |
| Scala Pattern | Roc Pattern | Conceptual Translation |
|---|---|---|
for comprehension | Pipeline |> or nested when | Monadic composition becomes explicit |
| Pattern matching | when expression | Similar syntax, structural matching |
| Case class | Record type | Structural types, automatic equality |
| Sealed trait ADT | Tag union | Sum types with exhaustiveness |
| Implicit conversion | No equivalent | Explicit conversions preferred |
| Higher-order function | Function types | Direct support, same concept |
| Scala | Roc | Notes |
|---|---|---|
Byte | I8 | 8-bit signed |
Short | I16 | 16-bit signed |
Int | I32 | 32-bit signed (common) |
Long | I64 | 64-bit signed |
Float | F32 | 32-bit float |
Double | F64 | 64-bit float |
Boolean | Bool | Direct mapping |
Char | U32 | Unicode scalar value |
String | Str | UTF-8 strings |
Unit | {} | Empty record |
Nothing | - | No direct equivalent |
Any | - | Avoid; use tag unions |
AnyVal | - | Not needed in Roc |
AnyRef | - | No reference types |
| Scala | Roc | Notes |
|---|---|---|
List[A] | List A | Immutable, efficient |
Vector[A] | List A | Roc List performs well |
Array[A] | List A | No mutable arrays |
Set[A] | Set A | Unique values |
Map[K, V] | Dict K V | Hash + Eq required for K |
Seq[A] | List A | General sequence → List |
IndexedSeq[A] | List A | Use List for indexing |
LazyList[A] | Generator pattern | Lazy evaluation via functions |
Option[A] | [Some A, None] | Optional values |
Either[L, R] | Result R L | Note: order reversed |
Try[A] | Result A [Err Str] | Exception handling |
| Scala | Roc | Notes |
|---|---|---|
case class User(...) | User : { name : Str, ... } | Records are structural |
sealed trait Color | Color : [Red, Green, Blue] | Sum types (ADTs) |
trait Service | Ability or module | Depends on use case |
object Utils | interface Utils | Module with functions |
(A, B) | (A, B) | Tuples map directly |
(A, B, C) | (A, B, C) | Multi-element tuples |
Generics [A] | Type parameters a | Similar concept |
Variance [+A] | - | Roc doesn't need variance |
| Scala | Roc | Notes |
|---|---|---|
() => R | {} -> R | Zero-arg function |
A => R | A -> R | Single arg |
(A, B) => R | A, B -> R | Multiple args |
Function1[A, B] | A -> B | Function type |
A => B => C | A -> (B -> C) | Curried functions |
By-name => A | {} -> A | Lazy evaluation |
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."
}
}
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."
Why this translation:
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..."
}
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..."
Why this translation:
Scala:
def findUser(id: Int, 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: Int, id2: Int): Option[(User, User)] = {
for {
user1 <- findUser(id1, users)
user2 <- findUser(id2, users)
} yield (user1, user2)
}
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
Why this translation:
[Some a, None]map/getOrElse → pattern matching or Result helperswhen expressionsScala:
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(_ * 2)
val evens = numbers.filter(_ % 2 == 0)
val sum = numbers.foldLeft(0)(_ + _)
// List comprehension
val squares = for {
x <- numbers
if x % 2 == 0
} yield x * x
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)
Why this translation:
foldLeft → List.walkfilter → List.keepIfScala:
def divide(a: Int, b: Int): Either[String, Int] = {
if (b == 0) Left("Division by zero")
else Right(a / b)
}
def calculate(a: Int, b: Int, c: Int): Either[String, Int] = {
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[Int] = Try(s.toInt)
def safeParse(s: String): Option[Int] = parseInt(s).toOption
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 equivalent
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
Why this translation:
Either[L, R] → Result ok err (note: order reversed)! for early returnsTry → Result with explicit error typestoOption → pattern matching to convert Result → Option-like tag unionScala:
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 print[A](a: A)(implicit s: Show[A]): Unit = {
println(s.show(a))
}
print(42) // Uses intShow
print("hello") // Uses stringShow
Roc:
# Roc abilities are automatic for basic types
# For custom behavior, use functions with ability constraints
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 }"
Why this translation:
where a implements AbilityScala:
def applyTwice[A](f: A => A, x: A): A = f(f(x))
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _
def compose[A, B, C](f: B => C, g: A => B): A => C = {
a => f(g(a))
}
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))
Why this translation:
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)
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,
}
Why this translation:
copy → Roc record update { record & field: value }Scala uses Futures for async computation on the JVM. Roc delegates all concurrency to the platform.
Scala:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def fetchUser(id: Int): Future[User] = Future {
// Async operation
database.query(s"SELECT * FROM users WHERE id = $id")
}
def fetchPosts(userId: Int): Future[List[Post]] = Future {
database.query(s"SELECT * FROM posts WHERE author = $userId")
}
// Composition
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)
)
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),
])
Why this translation:
! with TaskScala (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
}
}
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
Why this translation:
Scala:
package com.example.users
case class User(id: Int, 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")
}
}
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
Why this translation:
exposes listexposesScala (Anti-pattern in Roc):
var counter = 0
def increment(): Unit = { counter += 1 }
Roc Approach:
# No mutable state - return new value
increment : I64 -> I64
increment = \counter ->
counter + 1
# Usage
counter = 0
newCounter = increment(counter)
Why: Roc has no mutable variables. Always return new values.
Pitfall: Assuming Scala Vector performance in Roc.
Solution: Roc List is the primary collection. It's efficient for most use cases. Don't over-optimize based on JVM knowledge.
Scala:
var maybeUser: User = null // Avoid!
val user: Option[User] = Option(nullableValue)
Roc:
# No null! Use tag unions
maybeUser : [Some User, None]
maybeUser = None
# When converting from nullable source
userFromNullable : [Some User, None]
userFromNullable = Some({ name: "Alice", age: 30 })
Why: Roc has no null. Always use tag unions for optional values.
Pitfall: Scala Either[L, R] vs Roc Result ok err
Scala:
val result: Either[String, Int] = Right(42) // Right is success
Roc:
result : Result I64 Str # First param is success, second is error
result = Ok(42)
Why: Roc Result has opposite parameter order compared to Scala Either.
Pitfall: Scala's implicit conversions don't exist in Roc.
Solution: All conversions must be explicit:
# Explicit conversion required
intToStr : I64 -> Str
intToStr = Num.toStr
str = intToStr(42)
Pitfall: Trying to do I/O directly in application code.
Solution: Use platform-provided Tasks:
# Wrong - no direct I/O
# readFile("path") # This doesn't exist!
# Correct - platform Task
import pf.File
import pf.Task exposing [Task]
readFile : Str -> Task Str [FileErr]
readFile = \path ->
File.readUtf8(path)
Why: Roc applications are pure. All effects go through the platform.
| Purpose | Scala | Roc | Notes |
|---|---|---|---|
| Build tool | sbt, Mill, Maven | roc CLI | Roc has built-in build |
| Package manager | sbt, Maven | Platform dependencies | Platforms are URLs |
| Testing | ScalaTest, MUnit | roc test | Inline expect statements |
| REPL | scala REPL | roc repl | Interactive evaluation |
| Formatter | Scalafmt | roc format | Built-in formatter |
| Type checking | scalac | roc check | Fast type checking |
| Documentation | Scaladoc | Comments in code | Markdown in interfaces |
Scala (Akka HTTP):
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import scala.concurrent.Future
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")
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:
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")
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:
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")
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)
| Aspect | Scala | Roc | Impact |
|---|---|---|---|
| Runtime | JVM (GC, JIT) | Native compilation | Roc generally faster startup, lower memory |
| Collections | Optimized for JVM | Native data structures | Different performance characteristics |
| Concurrency | Thread pool, async | Platform-managed | Depends on platform implementation |
| Memory | Heap-based, GC | Platform-managed | Lower overhead in Roc |
| Startup | JVM warmup time | Instant | Roc has no warmup period |
For more examples and patterns, see:
meta-convert-dev - Foundational patterns with cross-language exampleslang-scala-dev - Scala development patternslang-roc-dev - Roc development patternsconvert-erlang-roc - Similar functional language conversion (BEAM → Roc)Cross-cutting pattern skills:
patterns-concurrency-dev - Futures/Actors vs Tasks across languagespatterns-serialization-dev - JSON, validation across languagespatterns-metaprogramming-dev - Implicits vs abilities vs other approachesxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience