Python to Scala Functional Programming Translation
Higher-Order Functions
# Python
def apply_twice(f, x):
return f(f(x))
def make_multiplier(n):
return lambda x: x * n
double = make_multiplier(2)
result = apply_twice(double, 5) # 20
// Scala
def applyTwice[A](f: A => A, x: A): A = f(f(x))
def makeMultiplier(n: Int): Int => Int = x => x * n
val double = makeMultiplier(2)
val result = applyTwice(double, 5) // 20
Decorators → Function Composition
# Python
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
// Scala - function composition
def logCalls[A, B](f: A => B, name: String): A => B = { a =>
println(s"Calling $name")
val result = f(a)
println(s"Finished $name")
result
}
val add = (a: Int, b: Int) => a + b
val loggedAdd = logCalls(add.tupled, "add")
// Alternative: using by-name parameters
def withLogging[A](name: String)(block: => A): A = {
println(s"Calling $name")
val result = block
println(s"Finished $name")
result
}
Pattern Matching
# Python (3.10+)
def describe(value):
match value:
case 0:
return "zero"
case int(x) if x > 0:
return "positive int"
case int(x):
return "negative int"
case [x, y]:
return f"pair: {x}, {y}"
case {"name": name, "age": age}:
return f"{name} is {age}"
case _:
return "unknown"
// Scala - pattern matching is more powerful
def describe(value: Any): String = value match {
case 0 => "zero"
case x: Int if x > 0 => "positive int"
case _: Int => "negative int"
case (x, y) => s"pair: $x, $y"
case List(x, y) => s"list of two: $x, $y"
case m: Map[_, _] if m.contains("name") =>
s"${m("name")} is ${m("age")}"
case _ => "unknown"
}
// Case class pattern matching (preferred)
sealed trait Result
case class Success(value: Int) extends Result
case class Error(message: String) extends Result
def handle(result: Result): String = result match {
case Success(v) if v > 100 => s"Big success: $v"
case Success(v) => s"Success: $v"
case Error(msg) => s"Failed: $msg"
}
Option Handling (None/null Safety)
# Python
def find_user(user_id: int) -> Optional[User]:
user = db.get(user_id)
return user if user else None
def get_user_email(user_id: int) -> Optional[str]:
user = find_user(user_id)
if user is None:
return None
return user.email
# Chained operations
def get_user_city(user_id: int) -> Optional[str]:
user = find_user(user_id)
if user is None:
return None
address = user.address
if address is None:
return None
return address.city
// Scala - Option monad
def findUser(userId: Int): Option[User] = db.get(userId)
def getUserEmail(userId: Int): Option[String] =
findUser(userId).map(_.email)
// Chained operations with flatMap
def getUserCity(userId: Int): Option[String] =
findUser(userId)
.flatMap(_.address)
.map(_.city)
// For-comprehension (cleaner for multiple operations)
def getUserCity(userId: Int): Option[String] = for {
user <- findUser(userId)
address <- user.address
city <- Option(address.city)
} yield city
// Getting values out
val email = getUserEmail(1).getOrElse("no-email@example.com")
val emailOrThrow = getUserEmail(1).get // Throws if None
Generators → Iterators/LazyList
# Python
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Take first 10
fibs = list(itertools.islice(fibonacci(), 10))
// Scala - LazyList (was Stream in Scala 2.12)
def fibonacci: LazyList[BigInt] = {
def loop(a: BigInt, b: BigInt): LazyList[BigInt] =
a #:: loop(b, a + b)
loop(0, 1)
}
val fibs = fibonacci.take(10).toList
// Alternative: Iterator
def fibonacciIterator: Iterator[BigInt] = new Iterator[BigInt] {
private var (a, b) = (BigInt(0), BigInt(1))
def hasNext: Boolean = true
def next(): BigInt = {
val result = a
val newB = a + b
a = b
b = newB
result
}
}
Try/Either for Error Handling
# Python - exceptions
def parse_int(s: str) -> int:
try:
return int(s)
except ValueError:
return 0
# Python - Optional for errors
def safe_parse_int(s: str) -> Optional[int]:
try:
return int(s)
except ValueError:
return None
// Scala - Try monad
import scala.util.{Try, Success, Failure}
def parseInt(s: String): Try[Int] = Try(s.toInt)
val result = parseInt("123") match {
case Success(n) => s"Got: $n"
case Failure(e) => s"Error: ${e.getMessage}"
}
// Chaining Try operations
val doubled = parseInt("123").map(_ * 2)
// Either for custom error types
def parsePositive(s: String): Either[String, Int] = {
Try(s.toInt).toEither
.left.map(_ => "Not a number")
.flatMap { n =>
if (n > 0) Right(n)
else Left("Must be positive")
}
}
Function Composition
# Python
def compose(f, g):
return lambda x: f(g(x))
def pipe(*functions):
def inner(x):
result = x
for f in functions:
result = f(result)
return result
return inner
# Usage
add_one = lambda x: x + 1
double = lambda x: x * 2
pipeline = pipe(add_one, double, add_one) # (x + 1) * 2 + 1
// Scala - built-in composition
val addOne: Int => Int = _ + 1
val double: Int => Int = _ * 2
// compose: f.compose(g) = f(g(x))
val composed = addOne.compose(double) // addOne(double(x))
// andThen: f.andThen(g) = g(f(x))
val pipeline = addOne.andThen(double).andThen(addOne) // (x + 1) * 2 + 1
Currying and Partial Application
# Python
from functools import partial
def add(a, b, c):
return a + b + c
add_5 = partial(add, 5)
result = add_5(3, 2) # 10
// Scala - curried functions
def add(a: Int)(b: Int)(c: Int): Int = a + b + c
val add5 = add(5) _ // Partially applied
val result = add5(3)(2) // 10
// Converting between curried and uncurried
val uncurried = Function.uncurried(add _)
val curried = (uncurried _).curried
// Multiple parameter lists
def fold[A, B](init: B)(list: List[A])(f: (B, A) => B): B =
list.foldLeft(init)(f)
val sum = fold(0)(List(1, 2, 3))(_ + _)
Tail Recursion
# Python - no tail call optimization
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
# Workaround: iterative
def factorial_iter(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
// Scala - tail recursion with annotation
import scala.annotation.tailrec
def factorial(n: Int): BigInt = {
@tailrec
def loop(n: Int, acc: BigInt): BigInt = {
if (n <= 1) acc
else loop(n - 1, n * acc)
}
loop(n, 1)
}
Implicit Conversions and Type Classes
# Python - no direct equivalent
# Duck typing provides flexibility
// Scala - type classes via implicits (Scala 2) or given/using (Scala 3)
// Scala 3
trait Show[A]:
def show(a: A): String
given Show[Int] with
def show(a: Int): String = s"Int: $a"
def display[A](a: A)(using s: Show[A]): String = s.show(a)
// Scala 2
trait Show[A] {
def show(a: A): String
}
implicit val intShow: Show[Int] = new Show[Int] {
def show(a: Int): String = s"Int: $a"
}
def display[A](a: A)(implicit s: Show[A]): String = s.show(a)