Python scala builder pattern
Guide for translating Python fluent builder patterns and mutable accumulator objects to idiomatic immutable Scala builders with companion objects. Use this skill when converting Python classes with method chaining (returning self), mutable state accumulation, or factory/build() methods to Scala.From its SKILL.md
npx -y skills add cxcscmu/SkillLearnBench --skill python-scala-builder-patternAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
4.0 KB, 892 tokens by cl100k_base, as published. Nobody here has run it
Python Fluent Builder → Scala Immutable Builder
Python builders often use mutable self and return self for chaining. Scala's idiomatic approach uses immutable value objects — each with* method returns a new builder instance.
Pattern
class TokenizerBuilder(Generic[T]):
def __init__(self) -> None:
self._normalizers: list[Callable[[str], str]] = []
self._validators: list[Callable[[T], bool]] = []
self._metadata: dict[str, Any] = {}
def with_normalizer(self, norm) -> "TokenizerBuilder[T]":
self._normalizers.append(norm)
return self
def with_metadata(self, **kwargs) -> "TokenizerBuilder[T]":
self._metadata.update(kwargs)
return self
def build(self) -> Callable[[T], Token]:
...
// Scala: immutable builder; each method returns a NEW instance
class TokenizerBuilder[T] private (
normalizers: List[String => String],
validators: List[T => Boolean],
metadata: Map[String, Any]
) {
def withNormalizer(f: String => String): TokenizerBuilder[T] =
new TokenizerBuilder(normalizers :+ f, validators, metadata)
def withValidator(f: T => Boolean): TokenizerBuilder[T] =
new TokenizerBuilder(normalizers, validators :+ f, metadata)
def withMetadata(pairs: (String, Any)*): TokenizerBuilder[T] =
new TokenizerBuilder(normalizers, validators, metadata ++ pairs.toMap)
def build(): T => Token = { value =>
validators.foreach { v =>
if (!v(value)) throw new IllegalArgumentException(s"Validation failed for $value")
}
val str = normalizers.foldLeft(value.toString)((s, f) => f(s))
Token(str, TokenType.STRING, metadata)
}
}
// Companion object provides the public constructor
object TokenizerBuilder {
def apply[T](): TokenizerBuilder[T] =
new TokenizerBuilder[T](Nil, Nil, Map.empty)
}
Why immutable?
- Thread-safe by default — no shared mutable state
- Enables easy rollback (keep a reference to an old builder)
- Composable: builders can be passed around without defensive copying
Vararg Metadata
Python uses **kwargs for keyword metadata. In Scala, use (String, Any)* tuple varargs:
builder.withMetadata("type" -> "custom", "version" -> 1)
// Inside: metadata ++ pairs.toMap
Mutable Internal State → ListBuffer / mutable.Map
For cases where mutation is genuinely needed (e.g., MutableTokenBatch), use scala.collection.mutable:
class MutableTokenBatch:
def __init__(self):
self.tokens = []
self._processed = False
def add(self, token):
if self._processed:
raise RuntimeError("Batch already processed")
self.tokens.append(token)
class MutableTokenBatch {
private val _tokens = scala.collection.mutable.ListBuffer.empty[Token]
private var _processed = false
def tokens: List[Token] = _tokens.toList
def add(token: Token): Unit = {
if (_processed) throw new RuntimeException("Batch already processed")
_tokens += token
}
def markProcessed(): Unit = { _processed = true }
}
Python dict default argument anti-pattern → Scala safe defaults
# DANGER: mutable default in Python
def __init__(self, format_options: dict = {}): ...
In Scala, default parameter expressions are evaluated fresh each call:
class NumericTokenizer(
precision: Int = 6,
formatOptions: Map[String, Any] = Map.empty // safe — Map.empty is immutable
)
Build Output as a Function
Python build() returns a Callable. In Scala, the result is a plain function value:
val tokenize: T => Token = builder.build()
val result = tokenize("Hello World")
// or equivalently: builder.build()("Hello World")
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.