Agents convert-roc-scala
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.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/convert-roc-scala" ~/.claude/skills/arustydev-agents-convert-roc-scala && rm -rf "$T"
content/skills/convert-roc-scala/SKILL.mdRoc ↔ 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
- Foundational conversion patterns (APTV workflow, testing strategies)meta-convert-dev
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 |
|---|---|---|
/ | / | Specify bit width becomes JVM types |
| | 64-bit float |
| | Direct mapping |
| | UTF-8 → UTF-16 (JVM) |
| | Tag union → built-in type |
| | Note: order matches |
| or | Immutable lists |
| | Unique values |
| | Key-value map |
Record | | Records → case classes |
Tag union | | Sum types |
| Ability | (type class) | Type class pattern |
| | Platform-provided → JVM concurrency |
| | 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 | Reversed order possible |
expression | Pattern matching | 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 |
|---|---|---|
| | 8-bit signed |
| | 16-bit signed |
| | 32-bit signed (common) |
| | 64-bit signed |
| | 32-bit float |
| | 64-bit float |
| | Direct mapping |
| (careful with sign) | No unsigned in Scala 2, consider |
| | UTF-8 → UTF-16 |
| | Empty record |
| - | | No direct equivalent in Roc |
| - | | No direct equivalent in Roc |
Collection Types
| Roc | Scala | Notes |
|---|---|---|
| | Immutable, efficient prepend |
| | For random access, use Vector |
| | Unique values |
| | Key-value mapping |
| Generator pattern | or | Lazy evaluation |
| | Optional values |
| | Order matches |
Composite Types
| Roc | Scala | Notes |
|---|---|---|
| | Records → case classes |
| | Sum types |
| Ability or module | or | Depends on use case |
| | Module → singleton |
| | Tuples map directly |
Type parameters | Generics | Similar concept |
Function Types
| Roc | Scala | Notes |
|---|---|---|
| | Zero-arg function |
| | Single arg |
| | Multiple args (tuple) |
| | 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
→ Scala[Some a, None]Option[A] - Nested
→ for-comprehension (more ergonomic)when - Method chaining
/map
→ similar in ScalagetOrElse - 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
(method syntax).map
→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 err
(same order)Either[err, ok]- Try operator
→ for-comprehension for early returns! - Roc's explicit error tags → Scala's String or custom types
captures exceptions similar to Roc's result patternTry
converts Result/Either/Try to Option.toOption
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
andcompose
on functionsandThen - 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
→ Scala{ record & ... }
methodcopy - Default values can be in constructor directly
- Case class provides
automaticallycopy - 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
→ Scala visibility modifiersexposes - Not in exposes →
methodsprivate - 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 | CLI | sbt, Mill, Maven | Scala has multiple build tools |
| Package manager | Platform dependencies | sbt, Maven Central | Scala uses JVM ecosystem |
| Testing | | ScalaTest, MUnit, specs2 | Multiple testing frameworks |
| REPL | | REPL | Interactive exploration |
| Formatter | | Scalafmt | Configurable formatting |
| Type checking | | | 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:
- Foundational patterns with cross-language examplesmeta-convert-dev
- Roc development patternslang-roc-dev
- Scala development patternslang-scala-dev
- Similar functional language conversion (Clojure → Scala)convert-clojure-scala
Cross-cutting pattern skills:
- Tasks vs Futures/Actors vs other approachespatterns-concurrency-dev
- JSON, validation across languagespatterns-serialization-dev
- Abilities vs implicits vs other approachespatterns-metaprogramming-dev