Skillsbench python-scala-idioms
Guide for writing idiomatic Scala when translating from Python. Use when the goal is not just syntactic translation but producing clean, idiomatic Scala code. Covers immutability, expression-based style, sealed hierarchies, and common Scala conventions.
install
source · Clone the upstream repo
git clone https://github.com/benchflow-ai/skillsbench
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/benchflow-ai/skillsbench "$T" && mkdir -p ~/.claude/skills && cp -r "$T/tasks/python-scala-translation/environment/skills/python-scala-idioms" ~/.claude/skills/benchflow-ai-skillsbench-python-scala-idioms && rm -rf "$T"
manifest:
tasks/python-scala-translation/environment/skills/python-scala-idioms/SKILL.mdsource content
Python to Idiomatic Scala Translation
Core Principles
When translating Python to Scala, aim for idiomatic Scala, not literal translation:
- Prefer immutability - Use
overval
, immutable collectionsvar - Expression-based - Everything returns a value, minimize statements
- Type safety - Leverage Scala's type system, avoid
Any - Pattern matching - Use instead of if-else chains
- Avoid null - Use
,Option
,EitherTry
Immutability First
# Python - mutable by default class Counter: def __init__(self): self.count = 0 def increment(self): self.count += 1 return self.count
// Scala - immutable approach case class Counter(count: Int = 0) { def increment: Counter = copy(count = count + 1) } // Usage val c1 = Counter() val c2 = c1.increment // Counter(1) val c3 = c2.increment // Counter(2) // c1 is still Counter(0)
Expression-Based Style
# Python - statement-based def get_status(code): if code == 200: status = "OK" elif code == 404: status = "Not Found" else: status = "Unknown" return status
// Scala - expression-based def getStatus(code: Int): String = code match { case 200 => "OK" case 404 => "Not Found" case _ => "Unknown" } // No intermediate variable, match is an expression
Sealed Hierarchies for Domain Modeling
# Python - loose typing def process_payment(method: str, amount: float): if method == "credit": # process credit pass elif method == "debit": # process debit pass elif method == "crypto": # process crypto pass
// Scala - sealed trait for exhaustive matching sealed trait PaymentMethod case class CreditCard(number: String, expiry: String) extends PaymentMethod case class DebitCard(number: String) extends PaymentMethod case class Crypto(walletAddress: String) extends PaymentMethod def processPayment(method: PaymentMethod, amount: Double): Unit = method match { case CreditCard(num, exp) => // process credit case DebitCard(num) => // process debit case Crypto(addr) => // process crypto } // Compiler warns if you miss a case!
Replace Null Checks with Option
# Python def find_user(id): user = db.get(id) if user is None: return None profile = user.get("profile") if profile is None: return None return profile.get("email")
// Scala - Option chaining def findUser(id: Int): Option[String] = for { user <- db.get(id) profile <- user.profile email <- profile.email } yield email // Or with flatMap def findUser(id: Int): Option[String] = db.get(id) .flatMap(_.profile) .flatMap(_.email)
Prefer Methods on Collections
# Python result = [] for item in items: if item.active: result.append(item.value * 2)
// Scala - use collection methods val result = items .filter(_.active) .map(_.value * 2)
Avoid Side Effects in Expressions
# Python items = [] for x in range(10): items.append(x * 2) print(f"Added {x * 2}")
// Scala - separate side effects val items = (0 until 10).map(_ * 2).toList items.foreach(x => println(s"Value: $x")) // Or use tap for debugging val items = (0 until 10) .map(_ * 2) .tapEach(x => println(s"Value: $x")) .toList
Use Named Parameters for Clarity
# Python def create_user(name, email, admin=False, active=True): pass user = create_user("Alice", "alice@example.com", admin=True)
// Scala - named parameters work the same def createUser( name: String, email: String, admin: Boolean = false, active: Boolean = true ): User = ??? val user = createUser("Alice", "alice@example.com", admin = true) // Case class with defaults is often better case class User( name: String, email: String, admin: Boolean = false, active: Boolean = true ) val user = User("Alice", "alice@example.com", admin = true)
Scala Naming Conventions
| Python | Scala |
|---|---|
(variables, functions) | |
(constants) | or |
(classes) | |
| keyword |
| |
# Python MAX_RETRY_COUNT = 3 def calculate_total_price(items): pass class ShoppingCart: def __init__(self): self._items = []
// Scala val MaxRetryCount = 3 // or final val MAX_RETRY_COUNT def calculateTotalPrice(items: List[Item]): Double = ??? class ShoppingCart { private var items: List[Item] = Nil }
Avoid Returning Unit
# Python - None return is common def save_user(user): db.save(user) # implicit None return
// Scala - consider returning useful information def saveUser(user: User): Either[Error, UserId] = { db.save(user) match { case Right(id) => Right(id) case Left(err) => Left(err) } } // Or at minimum, use Try def saveUser(user: User): Try[Unit] = Try { db.save(user) }
Use Apply for Factory Methods
# Python class Parser: def __init__(self, config): self.config = config @classmethod def default(cls): return cls(Config())
// Scala - companion object with apply class Parser(config: Config) object Parser { def apply(config: Config): Parser = new Parser(config) def apply(): Parser = new Parser(Config()) } // Usage val parser = Parser() // Calls apply() val parser = Parser(customConfig)
Cheat Sheet: Common Transformations
| Python Pattern | Idiomatic Scala |
|---|---|
| or pattern match |
| or |
| |
| |
| |
| |
| |
| |
| |
| or pattern match |
| or pattern match |
| Mutable accumulator loop | / |
| |
Anti-Patterns to Avoid
// DON'T: Use null val name: String = null // Bad! // DO: Use Option val name: Option[String] = None // DON'T: Use Any or type casts val data: Any = getData() val name = data.asInstanceOf[String] // DO: Use proper types and pattern matching sealed trait Data case class UserData(name: String) extends Data val data: Data = getData() data match { case UserData(name) => // use name } // DON'T: Nested if-else chains if (x == 1) ... else if (x == 2) ... else if (x == 3) ... // DO: Pattern matching x match { case 1 => ... case 2 => ... case 3 => ... } // DON'T: var with mutation var total = 0 for (x <- items) total += x // DO: fold val total = items.sum // or val total = items.foldLeft(0)(_ + _)