home / skills / benchflow-ai / skillsbench / python-scala-idioms
/tasks/python-scala-translation/environment/skills/python-scala-idioms
This skill guides translating Python to idiomatic Scala, emphasizing immutability, pattern matching, and safe types for clean, reliable code.
npx playbooks add skill benchflow-ai/skillsbench --skill python-scala-idiomsReview the files below or copy the command above to add this skill to your agents.
---
name: python-scala-idioms
description: 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.
---
# Python to Idiomatic Scala Translation
## Core Principles
When translating Python to Scala, aim for idiomatic Scala, not literal translation:
1. **Prefer immutability** - Use `val` over `var`, immutable collections
2. **Expression-based** - Everything returns a value, minimize statements
3. **Type safety** - Leverage Scala's type system, avoid `Any`
4. **Pattern matching** - Use instead of if-else chains
5. **Avoid null** - Use `Option`, `Either`, `Try`
## Immutability First
```python
# Python - mutable by default
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
```
```scala
// 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
# Python - statement-based
def get_status(code):
if code == 200:
status = "OK"
elif code == 404:
status = "Not Found"
else:
status = "Unknown"
return status
```
```scala
// 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
# 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
// 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
# 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
// 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
# Python
result = []
for item in items:
if item.active:
result.append(item.value * 2)
```
```scala
// Scala - use collection methods
val result = items
.filter(_.active)
.map(_.value * 2)
```
## Avoid Side Effects in Expressions
```python
# Python
items = []
for x in range(10):
items.append(x * 2)
print(f"Added {x * 2}")
```
```scala
// 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
# Python
def create_user(name, email, admin=False, active=True):
pass
user = create_user("Alice", "[email protected]", admin=True)
```
```scala
// Scala - named parameters work the same
def createUser(
name: String,
email: String,
admin: Boolean = false,
active: Boolean = true
): User = ???
val user = createUser("Alice", "[email protected]", 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", "[email protected]", admin = true)
```
## Scala Naming Conventions
| Python | Scala |
|--------|-------|
| `snake_case` (variables, functions) | `camelCase` |
| `SCREAMING_SNAKE` (constants) | `CamelCase` or `PascalCase` |
| `PascalCase` (classes) | `PascalCase` |
| `_private` | `private` keyword |
| `__very_private` | `private[this]` |
```python
# Python
MAX_RETRY_COUNT = 3
def calculate_total_price(items):
pass
class ShoppingCart:
def __init__(self):
self._items = []
```
```scala
// 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
# Python - None return is common
def save_user(user):
db.save(user)
# implicit None return
```
```scala
// 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
# Python
class Parser:
def __init__(self, config):
self.config = config
@classmethod
def default(cls):
return cls(Config())
```
```scala
// 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 |
|---------------|-----------------|
| `if x is None` | `x.isEmpty` or pattern match |
| `if x is not None` | `x.isDefined` or `x.nonEmpty` |
| `x if x else default` | `x.getOrElse(default)` |
| `[x for x in xs if p(x)]` | `xs.filter(p)` |
| `[f(x) for x in xs]` | `xs.map(f)` |
| `any(p(x) for x in xs)` | `xs.exists(p)` |
| `all(p(x) for x in xs)` | `xs.forall(p)` |
| `next(x for x in xs if p(x), None)` | `xs.find(p)` |
| `dict(zip(keys, values))` | `keys.zip(values).toMap` |
| `isinstance(x, Type)` | `x.isInstanceOf[Type]` or pattern match |
| `try: ... except: ...` | `Try { ... }` or pattern match |
| Mutable accumulator loop | `foldLeft` / `foldRight` |
| `for i, x in enumerate(xs)` | `xs.zipWithIndex` |
## Anti-Patterns to Avoid
```scala
// 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)(_ + _)
```
This skill guides developers in producing idiomatic Scala when translating from Python. It focuses on Scala-first design: immutability, expression-based code, strong typing, and common Scala conventions rather than literal line-by-line translation. Use it to transform Python logic into clean, maintainable Scala that leverages the language's strengths.
The skill inspects Python constructs and recommends Scala patterns that preserve intent while adopting Scala idioms. It maps mutable patterns to immutable data structures, replaces null checks with Option/Either/Try, suggests sealed hierarchies for domain modeling, and converts loops and comprehensions into collection operations and folds. It also enforces naming and API conventions to make translated code feel native to Scala developers.
Should I always use immutable data structures?
Prefer immutability for correctness and ease of reasoning. Use mutation only when performance needs are proven and encapsulated.
How do I handle exceptions when translating try/except?
Use Try for local operations that may fail, and Either or custom ADTs for domain-level errors to avoid throwing exceptions across boundaries.