AGENT SKILL

run2_scala-type-classes

Translating Python Protocol, ABC, and isinstance dispatch to Scala traits, overloaded methods, and functor/monad abstractions.

License 许可
MIT
最近更新
2026-10-02
Stars
★ 92
Forks
7

01 前言 Front Matter

name
run2_scala-type-classes
description
Translating Python Protocol, ABC, and isinstance dispatch to Scala traits, overloaded methods, and functor/monad abstractions.

03 SKILL.md 正文

Scala Traits, Overloading, and Functors (vs Python Protocol/ABC/isinstance)

Python Protocol → Scala Trait

Python structural typing (duck typing):

@runtime_checkable
class Tokenizable(Protocol):
    def to_token(self) -> str: ...

Scala uses nominal typing — classes must explicitly declare extends Tokenizable:

trait Tokenizable {
  def toToken: String
}
  • No @runtime_checkable — Scala trait membership is checked at compile time
  • Method name convention: toToken (camelCase) not to_token

Python ABC → Scala Abstract Class

class BaseTokenizer(ABC, Generic[T]):
    @abstractmethod
    def tokenize(self, value: T) -> Token: ...
    def tokenize_batch(self, values: Iterable[T]) -> Iterator[Token]:
        for v in values: yield self.tokenize(v)
abstract class BaseTokenizer[T] {
  def tokenize(value: T): Token                          // abstract (no body)
  def tokenizeBatch(values: Iterable[T]): Iterator[Token] =
    values.iterator.map(tokenize)                        // concrete default
}
  • Methods without body are abstract — no @abstractmethod annotation needed
  • values.iterator.map(tokenize) is lazy — same semantics as Python generator

Python isinstance Dispatch → Scala Method Overloading

Python runtime dispatch with type checks:

def tokenize(self, value: Any) -> Token:
    if value is None: return Token("NULL", TokenType.NULL)
    if isinstance(value, (str, bytes)): return self._string_tokenizer.tokenize(value)
    if isinstance(value, (int, float, Decimal)): return self._numeric_tokenizer.tokenize(value)
    if isinstance(value, (datetime, date)): return self._temporal_tokenizer.tokenize(value)

Scala compile-time overloading:

final class UniversalTokenizer {
  private val stringTokenizer   = new StringTokenizer()
  private val numericTokenizer  = new NumericTokenizer()
  private val temporalTokenizer = new TemporalTokenizer()

  def tokenize(value: String):        Token = stringTokenizer.tokenize(value)
  def tokenize(value: Array[Byte]):   Token = stringTokenizer.tokenizeBytes(value)
  def tokenize(value: Int):           Token = numericTokenizer.tokenizeInt(value)
  def tokenize(value: Double):        Token = numericTokenizer.tokenizeDouble(value)
  def tokenize(value: BigDecimal):    Token = numericTokenizer.tokenize(value)
  def tokenize(value: LocalDateTime): Token = temporalTokenizer.tokenize(value)
  def tokenize(value: LocalDate):     Token = temporalTokenizer.tokenizeDate(value)
  def tokenize(value: Tokenizable):   Token = Token(value.toToken, TokenType.STRUCTURED)
  def tokenizeNull: Token = Token("NULL", TokenType.NULL)   // null has no value in Scala
}
  • Each overload is resolved at compile time — no runtime overhead
  • None → tokenizeNull (separate method, since Scala null is not a type)

TokenFunctor and TokenMonad

class TokenFunctor[T](private val _value: T) {
  def get: T = _value
  def map[B](f: T => B): TokenFunctor[B]           = new TokenFunctor(f(_value))
  def flatMap[B](f: T => TokenFunctor[B]): TokenFunctor[B] = f(_value)
  def getOrElse[B >: T](default: => B): B = Option(_value).getOrElse(default)
}

Key improvements over Python:

  • map and flatMap are properly typed with B — not Any
  • get is the primary value accessor
  • getOrElse uses call-by-name default — only evaluated if needed
  • [B >: T] lower bound in getOrElse preserves type safety
class TokenMonad[T](value: T) extends TokenFunctor[T](value) {
  def ap[B](funcWrapped: TokenMonad[T => B]): TokenMonad[B] =
    new TokenMonad(funcWrapped.get(get))
}

object TokenMonad {
  def pure[T](value: T): TokenMonad[T] = new TokenMonad(value)
}
  • pure lives in companion object — replaces Python @classmethod
  • ap properly typed: accepts TokenMonad[T => B], returns TokenMonad[B]

TokenRegistry: Handler Pipeline

Python for/else semantics (first successful handler wins):

for handler in self._handlers:
    result = handler(item)
    if result is not None:
        results.append(result); break
else:
    results.append(None)

Scala with lazy iterator short-circuit:

handlers.iterator.flatMap(h => h(item)).nextOption()
  • h(item) returns Option[Token]
  • flatMap on Iterator[Option[Token]] flattens to Iterator[Token], skipping None
  • .nextOption() takes only the first result — lazy, so remaining handlers NOT evaluated
  • Returns Option[Token] — None if all handlers returned None

04 同仓库更多 Skills