Skip to content

Scala チートシート

JVM 上のハイブリッド関数型/OO 言語。

01

基礎

変数と型

より安全で予測可能なコードのために var(可変)より val(不変)を優先してください。Scala は型を推論しますが、公開 API の可読性のために明示的なアノテーションが役立ちます。すべてがオブジェクトです—プリミティブはありません(Int、Boolean はクラスです)。

scala
val name = "Alice"   // immutable (preferred)
var age = 30          // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List[Int] = List(1, 2, 3)
println(name.getClass)  // class java.lang.String

文字列補間

s"..." が ${expr} 補間を有効にします。f"..." が printf スタイルのフォーマット(%s、%.2f)を追加します。raw"..." がエスケープシーケンスを無効にします。これらのプレフィックスにより文字列構築が型安全で読みやすくなります。

scala
val name = "Alice"
val age = 30
println(s"Name: ${name}, Age: ${age}")
println(s"Next year: ${age + 1}")
println(s"Upper: ${name.toUpperCase}")
println(f"${name}%s weighs ${65.5}%.1f kg")  // formatted
println(raw"No \n escape")  // raw string

タプル

タプルは2〜22の異種値をグループ化します。_1、_2(1始まり)でアクセスします。val (a, b) = tuple で分解します。3要素以上の場合、名前付きフィールドとより良い可読性のために case class を優先してください。

scala
val pair = ("Alice", 30)
println(pair._1)  // Alice
println(pair._2)  // 30
val (name, age) = pair  // destructure
println(s"${name}: ${age}")
// Scala 3: val p = ("a", 1, 2.0)
val triple = ("a", 1, 2.0)
println(triple._3)  // 2.0

型推論と型アスクリプション

Scala はローカル変数と戻り値の型を推論します。公開 API、再帰関数、曖昧なケースには明示的な型を使用してください。型アスクリプション(expr: Type)が型を強制します—アップキャストや曖昧さ回避に有用です。

scala
val x = 42           // Int inferred
val y: Long = 42      // explicit Long
val z = 42: Long      // type ascription
val list = List(1, 2, 3)  // List[Int]
val mixed: List[Any] = List(1, "a", true)
def double(x: Int) = x * 2  // return type inferred

Unit と Nothing

Unit は void のようなものです(値は () のみ)。Nothing はインスタンスのないボトム型です—戻らない関数(throw、無限ループ)に使用されます。Nothing はすべての型のサブタイプで、柔軟な型推論を可能にします。

scala
def printIt(x: Int): Unit = println(x)  // like void
val u: Unit = ()    // Unit has one value: ()
// Nothing is the bottom type—no instances
def error(msg: String): Nothing =
  throw new RuntimeException(msg)
// Nothing is a subtype of everything
val n: Nothing = error("boom")
02

文字列

一般的な文字列メソッド

Scala の文字列は Java の文字列に暗黙変換(StringOps)経由で追加メソッドを持つものです。ほとんどのメソッドは新しい文字列を返します(不変)。明確さと正確性のために手動ループの代わりにこれらを使用してください。

scala
val s = "Hello, World"
println(s.length)        // 12
println(s.toUpperCase)   // HELLO, WORLD
println(s.toLowerCase)   // hello, world
println(s.split(", "))   // Array(Hello, World)
println(s.replace("o", "0"))  // Hell0, W0rld
println(s.reverse)       // dlroW ,olleH
println(s.contains("World"))  // true

複数行文字列(三重引用符)

三重引用符文字列はすべての空白と改行を保持します。stripMargin と | を使用してコードをきれいに整列させます—| の後のテキストのみが保持されます。SQL、JSON、コードに埋め込まれたテンプレートに最適です。

scala
val sql = """
  SELECT * FROM users
  WHERE age > 18
  ORDER BY name
"""
println(sql.trim)
// StripMargin for clean indentation
val text = """|Hello
              |World""".stripMargin
println(text)  // Hello
World

文字列構築

mkString がコレクションをオプションのプレフィックス/サフィックス付きで結合します—慣用的で効率的です。ループ内で大きな文字列を構築するには StringBuilder を使用してください。ループ内での + の繰り返しは避けてください(多くの中間オブジェクトを作成します)。

scala
val parts = List("apple", "banana", "cherry")
println(parts.mkString(", "))     // apple, banana, cherry
println(parts.mkString("[", ", ", "]"))  // [apple, banana, cherry]
val sb = new StringBuilder
for (p <- parts) sb.append(p).append(" ")
println(sb.toString.trim)

文字列から数値へ

toInt/toDouble は無効な入力で例外を投げます。安全な解析には toIntOption(Scala 2.13+)を使用し Option を返します。一括解析には Try または Either を使用し、例外なしに関数的にエラーを処理します。

scala
val n = "42".toInt        // 42
val d = "3.14".toDouble    // 3.14
val b = "true".toBoolean   // true
val safe = "abc".toIntOption  // Some(42) or None
// Handling errors
val result = try "x".toInt catch { case _ => 0 }
println(result)  // 0

正規表現

.r が文字列を Regex に変換します。findFirstIn は Option を返し、findAllIn はイテレータを返します。抽出には case email(e) => でパターンマッチングを使用します。Regex は内部では Java の Pattern です。

scala
import scala.util.matching.Regex
val email: Regex = "[\w.]+@[\w]+\.[a-z]+".r
val text = "Contact: [email protected]"
email.findFirstIn(text) match {
  case Some(e) => println(s"Found: ${e}")
  case None => println("No email")
}
val replaced = "[0-9]+".r.replaceAllIn("a1b2c3", "#")
println(replaced)  // a#b#c#
03

データ構造

List と Seq

List は不変の片方向リンクリストです—head/prepend は O(1)、ランダムアクセスは O(n) です。ランダムアクセスには Vector を使用してください(実質 O(1))。+: が先頭に追加、:+ が末尾に追加します。スレッド安全性のため不変コレクションを優先してください。

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.head)      // 1
println(nums.tail)      // List(2,3,4,5)
println(nums.reverse)   // List(5,4,3,2,1)
println(nums.take(2))   // List(1, 2)
println(nums.drop(2))   // List(3, 4, 5)
println(nums.mkString)  // 12345
val combined = 0 +: nums :+ 6  // List(0,1,2,3,4,5,6)

Map

Map は不変です—操作は新しい Map を返します。get(key) で Option アクセス、getOrElse でデフォルト値です。+ が追加/更新、- が削除します。可変マップには scala.collection.mutable.Map を使用してください。キーは Hashable でなければなりません。

scala
val ages = Map("Alice" -> 30, "Bob" -> 25)
println(ages("Alice"))            // 30 (throws if missing)
println(ages.getOrElse("Eve", 0)) // 0 (safe)
val updated = ages + ("Eve" -> 28)  // new Map
val removed = ages - "Bob"
ages.foreach { case (k, v) => println(s"${k}: ${v}") }
println(ages.keys)  // Set(Alice, Bob)

Set

Set は不変で contains が O(1) です。union(|)、intersect(&)、diff(~)で集合演算します。+ が追加、- が削除します。重複排除とメンバーシップテストに使用してください。可変版:scala.collection.mutable.Set。

scala
val a = Set(1, 2, 3)
val b = Set(3, 4, 5)
println(a union b)        // Set(1,2,3,4,5)
println(a intersect b)    // Set(3)
println(a diff b)         // Set(1,2)
println(a subsetOf(Set(1,2,3,4)))  // true
val added = a + 6  // Set(1,2,3,6)

Option(Null 安全性)

Option が null を置き換えます—Some(value) または None です。変換には map/filter/flatMap を、デフォルトには getOrElse を使用します。for 内包表記は Option で動作します。これにより型における不在を明示的にすることで NullPointerException を排除します。

scala
def findUser(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
val name = findUser(1)
println(name.getOrElse("Unknown"))  // Alice
println(name.map(_.toUpperCase))    // Some(ALICE)
println(name.filter(_.startsWith("A")))  // Some(Alice)
val result = for {
  n <- findUser(1)
  if n.startsWith("A")
} yield n.toUpperCase  // Some(ALICE)

Array と Vector

Array は可変の Java 配列です(最速ですが関数型更新はありません)。Vector は不変で実質 O(1) のランダムアクセスと更新を持ちます—不変ランダムアクセスコレクションに推奨されます。シーケンシャルには List、インデックス付きには Vector を使用してください。

scala
val arr = Array(1, 2, 3, 4)  // mutable, Java array
arr(0) = 10
println(arr(0))  // 10
val vec = Vector(1, 2, 3, 4)  // immutable, fast random access
println(vec(2))  // 3
val updated = vec.updated(0, 10)  // Vector(10,2,3,4)
// Vector: O(1) random access + immutable
// Array: mutable, Java interop, fastest
04

制御フロー

If / Else(式)

Scala では if/else は値を返す式です。これにより三項演算子が不要になります。両方のブランチは互換性のある型でなければなりません。簡潔な条件付き代入にこれを使用してください。

scala
val score = 85
val grade =
  if (score >= 90) "A"
  else if (score >= 80) "B"
  else if (score >= 70) "C"
  else "F"
println(grade)  // B
// if returns a value—no ternary operator needed

For 内包表記

for 内包表記は反復し、フィルタ(if ガード)と変換(yield)が可能です。yield なしではループ、yield ありではコレクションを構築します。to は終端を含み、until は除外します。flatMap/map/filter チェーンと同等です。

scala
for (i <- 1 to 5) println(i)  // 1 2 3 4 5
for (i <- 1 until 5) print(i)  // 1 2 3 4
// With yield (generates collection)
val doubled = for (n <- List(1,2,3)) yield n * 2
println(doubled)  // List(2, 4, 6)
// With filter (guard)
val evens = for (n <- 1 to 10 if n % 2 == 0) yield n
println(evens.toList)  // List(2,4,6,8,10)

Match(パターンマッチング)

match は Scala の強力なパターンマッチングです—switch の強化版のようなものです。リテラル、OR(|)、ガード(if)、型マッチング、分解をサポートします。網羅的でなければなりません(コンパイラが未カバーのケースで警告します)。値を返す式です。

scala
val n = 2
val label = n match {
  case 0 => "zero"
  case 1 | 2 | 3 => "small"
  case x if x < 10 => "medium"
  case _ => "large"
}
println(label)  // small
// Match on types
def describe(x: Any): String = x match {
  case i: Int => s"Int: ${i}"
  case s: String => s"String: ${s}"
  case _ => "unknown"
}

While と Do-While

while/do-while は Unit を返す命令型ループです。可変状態(var)が必要です。慣用的な関数型 Scala のためには for 内包表記または再帰を優先してください。パフォーマンスが要求される場合や副作用にのみ while を使用してください。

scala
var i = 0
while (i < 3) {
  println(i)
  i += 1
}
var j = 0
do {
  println(j)
  j += 1
} while (j < 3)
// Prefer recursion or for-comprehensions for immutability

Try / Catch / Finally

try/catch/finally は Java に似ていますが catch でパターンマッチングを使用します。関数型エラー処理には Try を優先してください—例外を Success/Failure 値としてラップし、try/catch のボイラープレートなしで map/flatMap チェーンを可能にします。

scala
import scala.util.{Try, Success, Failure}
val result = try {
  "abc".toInt
} catch {
  case e: NumberFormatException => 0
} finally {
  println("cleanup")
}
println(result)  // 0
// Functional alternative
val r2 = Try("abc".toInt).getOrElse(0)
println(r2)  // 0
05

関数

メソッド定義

メソッドは def name(params): ReturnType = body を使用します。デフォルトパラメータと名前付き引数をサポートします。Unit 戻り値 = 副作用のみ。単一式の本体は中括弧を省略します。= は必須です(なしの場合は Unit を返すプロシージャになります)。

scala
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
  s"${greeting}, ${name}!"
def log(msg: String): Unit = println(msg)
println(add(3, 4))           // 7
println(greet("Alice"))      // Hello, Alice!
println(greet("Bob", greeting = "Hi"))  // named arg

ラムダ(匿名関数)

ラムダ:(params) => body。単一パラメータの略記として _ を使用(x => x * 2 が _ * 2 に)。複数の _ は異なるパラメータを参照します(_ + _)。ラムダは第一級です—map、filter、reduce などに渡せます。

scala
val square = (x: Int) => x * x
println(square(5))  // 25
val nums = List(1, 2, 3)
println(nums.map(_ * 2))      // List(2, 4, 6)
println(nums.filter(_ > 1))   // List(2, 3)
println(nums.reduce(_ + _))   // 6
// _ is shorthand for the parameter

高階関数

高階関数は関数を取るまたは返します。これにより強力な抽象化が可能になります:map/filter/reduce、合成、部分適用。makeAdder は n をキャプチャするクロージャを返します。これが関数型プログラミングの心臓部です。

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(applyTwice(_ + 3, 5))  // 11
def makeAdder(n: Int): Int => Int = _ + n
val add5 = makeAdder(5)
println(add5(10))  // 15
// Functions returning functions = currying-like

カリー化と部分適用

カリー化はパラメータを複数のリストに分割します:def f(a)(b)。_ で部分適用して特殊化された関数を作成します。複数パラメータリストは型推論を改善します(コンパイラがリストから f の型を推論可能)。コレクション API で一般的です。

scala
def add(a: Int)(b: Int): Int = a + b  // curried
val add5 = add(5)_  // partially applied
println(add5(3))  // 8
// Multiple parameter lists
def foldLeft[A, B](list: List[A])(z: B)(f: (B, A) => B): B = ???
// Helps type inference
val sum = List(1,2,3).foldLeft(0)(_ + _)

名前呼びと Lazy

名前呼びパラメータ(=> T)は使用ごとに遅延評価されます—ロギング(無効時の高価なメッセージをスキップ)やカスタム制御構造に有用です。lazy val は初回アクセスまで初期化を遅延させます—高価な値やオプションの値に使用します。

scala
// By-name parameter: evaluated on each use
def debug(msg: => String): Unit =
  if (debugEnabled) println(msg)
// Lazy evaluation
lazy val expensive = computeHeavy()
println(expensive)  // computed now
def computeHeavy(): Int = { println("computing"); 42 }
06

クラスと OOP

クラスとコンストラクタ

主コンストラクタのパラメータはクラスシグネチャにあります。val パラメータは不変フィールド(公開 getter)、var は可変になります。クラス本体がコンストラクタです。補助コンストラクタは def this(...) を使用し、別のコンストラクタを呼び出さなければなりません。

scala
class Person(val name: String, val age: Int) {
  // Constructor params with val/var become fields
  def greet: String = s"Hi, I'm ${name}"
  def isAdult: Boolean = age >= 18
}
val p = new Person("Alice", 30)
println(p.greet)   // Hi, I'm Alice
println(p.name)    // Alice (val field)
println(p.isAdult) // true

Case Class

Case class は不変のデータクラスで、equals、hashCode、toString、copy、apply/unapply を持つコンパニオンオブジェクトが自動生成されます。データモデリングとパターンマッチングに使用します。'new' は不要です。Scala の ADT の基盤です。

scala
case class Point(x: Int, y: Int)
val p1 = Point(3, 4)  // no 'new' needed
val p2 = Point(3, 4)
println(p1 == p2)  // true (value equality)
val moved = p1.copy(x = 5)  // Point(5, 4)
println(p1.x, p1.y)  // 3 4 (fields auto-visible)
// Auto: equals, hashCode, toString, copy, companion

Trait(実装付きインターフェース)

Trait は Java のインターフェースに似ていますが実装を持てます。クラスは複数の trait をミックスインできます(extends/with で)。Trait は振る舞いの多重継承を可能にします。共有インターフェース、ミックスイン、線形化による積み重ね可能な変更に使用します。

scala
trait Greetable {
  def name: String  // abstract
  def greet: String = s"Hello, ${name}"  // concrete
}
trait Named {
  val name: String
}
class User(val name: String) extends Greetable
val u = new User("Alice")
println(u.greet)  // Hello, Alice
// Stackable traits via linearization

Object(シングルトン)

object はシングルトン(1つのインスタンス)を宣言します。コンパニオンオブジェクト(クラスと同名)は静的ライクなメソッド、ファクトリ(apply)、抽出子(unapply)を保持します。呼び出し元が 'new' を省略できるようファクトリメソッドに apply を使用してください。これが慣用的な Scala です。

scala
object Config {
  val version = "1.0"
  def load(): Map[String, String] = Map("key" -> "value")
}
println(Config.version)  // 1.0
// Companion object (same name as class)
class Person(val name: String)
object Person {
  def apply(name: String): Person = new Person(name)
}
val p = Person("Alice")  // uses apply, no 'new'

継承と Abstract Class

abstract class は未実装のメンバーを持てます。extends で継承、override で再定義します。ミックスインには trait を優先してください(多重継承)。コンストラクタパラメータが必要な場合や基本型が必要な場合に abstract class を使用します。クラスの単一継承です。

scala
abstract class Shape {
  def area: Double  // abstract
  def describe: String = s"Area: ${area}"
}
class Circle(r: Double) extends Shape {
  def area: Double = math.Pi * r * r
}
val c = new Circle(5)
println(c.describe)  // Area: 78.53...
// override required for concrete members
// abstract class vs trait: use abstract for base, trait for mixins
07

コレクションと関数型

Map / Filter / Fold

map が変換、filter が選択、reduce/foldLeft が集約します。これらが関数型データ変換のコアです。foldLeft はシードを取り結合的です、reduce は非空を必要とします。明確さと不変性のためにループの代わりにこれらを使用してください。

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.map(_ * 2))           // List(2,4,6,8,10)
println(nums.filter(_ % 2 == 0))   // List(2,4)
println(nums.reduce(_ + _))        // 15
println(nums.foldLeft(0)(_ + _))   // 15
println(nums.sum)                  // 15
println(nums.mkString(", "))       // 1, 2, 3, 4, 5

FlatMap と For 内包表記

flatMap はマップとフラット化を1ステップで行います—ネストしたコレクションとモナド操作に不可欠です。for 内包表記は flatMap/map/filter チェーンの糖衣構文です。複雑なネスト変換に使用してください—より読みやすいです。

scala
val nested = List(List(1, 2), List(3, 4))
println(nested.flatten)        // List(1,2,3,4)
println(nested.flatMap(_.map(_ * 2)))  // List(2,4,6,8)
// Equivalent for-comprehension:
val result = for {
  inner <- nested
  n <- inner
} yield n * 2
println(result)  // List(2,4,6,8)

グループ化とソート

groupBy がキーで Map に分割します。sorted は自然順序、sortBy はキー関数、sortWith はコンパレータでソートします。これらは新しいコレクションを返します(不変)。データ分析、分類、順序付けに使用します。

scala
val words = List("apple", "bat", "cat", "ant")
val byFirst = words.groupBy(_.head)
// Map(a -> List(apple, ant), b -> List(bat), c -> List(cat))
println(byFirst)
val sorted = words.sorted  // List(ant, apple, bat, cat)
val byLen = words.sortBy(_.length)  // List(ant, bat, cat, apple)
val desc = words.sortWith(_ > _)  // descending
println(sorted, byLen)

Either と Try

Try は例外を Success/Failure 値としてラップします。Either は Left(エラー)または Right(成功)を表します—エラー型を区別したいドメインエラーに使用します。両方とも map/flatMap をサポートし関数型エラー伝播を可能にします。

scala
import scala.util.{Try, Success, Failure}
def parse(s: String): Try[Int] = Try(s.toInt)
parse("42") match {
  case Success(n) => println(s"OK: ${n}")
  case Failure(e) => println(s"Err: ${e.getMessage}")
}
// Either for domain errors
def divide(a: Int, b: Int): Either[String, Int] =
  if (b == 0) Left("div by zero") else Right(a / b)
println(divide(10, 2))  // Right(5)
println(divide(10, 0))  // Left(div by zero)

遅延コレクション(View と Lazy)

.view はコレクションを遅延にします—操作は強制されるまで(toList、sum など)遅延されます。大きなデータのパフォーマンス向上のため中間コレクションを回避します。LazyList(旧 Stream)は無限シーケンスを可能にします—要素はオンデマンドで計算されます。

scala
val nums = (1 to 1000000).view
val result = nums
  .filter(_ % 2 == 0)
  .map(_ * 2)
  .take(5)
  .toList  // forces evaluation
println(result)  // List(4, 8, 12, 16, 20)
// view = lazy, no intermediate collections
// LazyList (Stream) for infinite sequences
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
08

パターンマッチング

Case Class のマッチング

Sealed trait + case class で代数的データ型(ADT)を構成します。コンパイラが網羅性をチェックします—ケースを追加するとマッチの更新が必要な場所で警告します。パターンマッチングは case class を直接分解します。これが慣用的な Scala モデリングです。

scala
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Square(s: Double) extends Shape
case class Rect(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
  case Circle(r) => math.Pi * r * r
  case Square(s) => s * s
  case Rect(w, h) => w * h
}
println(area(Circle(5)))  // 78.53...

ガードと条件

ガード(if condition)がパターンにランタイムチェックを追加します。マッチングをより表現力豊かにします。順序が重要です—最初のマッチが勝ちます。単純なパターンで表現できない範囲、条件、複雑なロジックにガードを使用します。

scala
val n = 15
val desc = n match {
  case x if x < 0 => "negative"
  case 0 => "zero"
  case x if x % 2 == 0 => "even"
  case _ => "odd"
}
println(desc)  // odd
// Guards add boolean conditions to patterns

コレクションのマッチング

:: cons パターンがリストを head と tail に分解します。_* は配列/リストの0個以上の要素にマッチします。これらのパターンは再帰的リスト処理と構造的分解を可能にします。解析とツリートラバーサルに強力です。

scala
val list = List(1, 2, 3, 4)
list match {
  case Nil => println("empty")
  case head :: Nil => println(s"one: ${head}")
  case head :: tail => println(s"head=${head}, rest=${tail}")
  case _ => println("other")
}
// head :: tail destructures a list
val arr = Array(1, 2, 3)
arr match {
  case Array(1, _*) => println("starts with 1")
  case _ => println("other")
}

Option と Either のマッチング

Option/Either のパターンマッチングは慣用的です—Some(x)/None、Right(x)/Left(e)。for 内包表記は match 付きの flatMap に脱糖されます。これにより明示的な if-else チェックなしでエラー処理が自然に流れます。

scala
def find(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
find(1) match {
  case Some(name) => println(s"Found: ${name}")
  case None => println("Not found")
}
// In for-comprehensions
val result = for {
  name <- find(1)
  upper = name.toUpperCase
} yield upper
println(result)  // Some(ALICE)

抽出子(unapply)

unapply によるカスタム抽出子は任意の型でパターンマッチングを可能にします。unapply メソッドは抽出値の Option を返します。これにより独自のパターンを定義できます—DSL と解析に強力です。Case class は unapply を自動生成します。

scala
object Email {
  def unapply(s: String): Option[(String, String)] = {
    val parts = s.split("@")
    if (parts.length == 2) Some((parts(0), parts(1))) else None
  }
}
"[email protected]" match {
  case Email(user, domain) =>
    println(s"User: ${user}, Domain: ${domain}")
  case _ => println("Not an email")
}
09

ジェネリクスと Implicit

ジェネリッククラスとメソッド

ジェネリクス(型パラメータ [A])で型安全で再利用可能なコードを書きます。単一型には [A]、2つには [A, B] を使用します。型推論が通常型を推測します。ジェネリクスはランタイムで消去されます(JVM の制限)がコンパイル時にチェックされます。

scala
class Stack[A] {
  private var items: List[A] = Nil
  def push(x: A): Unit = { items = x :: items }
  def pop: Option[A] = items.headOption
}
val s = new Stack[Int]
s.push(1); s.push(2)
println(s.pop)  // Some(2)
def first[A](list: List[A]): Option[A] = list.headOption
println(first(List("a", "b")))  // Some(a)

型境界

<: 上限境界(A はサブタイプ)、>: 下限境界(A はスーパータイプ)。コンテキスト境界(A: Ordering)はその型の implicit 値を要求します。ビュー境界(A <% B)は非推奨です—代わりにコンテキスト境界を使用してください。これらは型パラメータを制約します。

scala
// Upper bound: A must be Animal or subclass
class Box[A <: Animal](val content: A)
// Lower bound: A must be Dog or superclass
class Kennel[A >: Dog](val occupant: A)
// Context bound: A must have an Ordering
def max[A: Ordering](a: A, b: A): A =
  if (implicitly[Ordering[A]].gt(a, b)) a else b
abstract class Animal { def name: String }
class Dog extends Animal { def name = "Rex" }

Implicit パラメータ

Implicit パラメータはコンパイラがスコープから注入します。設定、型クラス、またはどこにでも渡したくない依存関係に使用します。implicit val/def で宣言します。コンパイラは外側のスコープとコンパニオンオブジェクトを検索します。

scala
def greet(name: String)(implicit greeting: String): String =
  s"${greeting}, ${name}!"
implicit val defaultGreeting: String = "Hello"
println(greet("Alice"))  // Hello, Alice! (implicit injected)
println(greet("Bob")("Hi"))  // Hi, Bob! (explicit override)
// Compiler finds implicit in scope

型クラス(Implicit 変換)

型クラス(implicit 経由)は型を変更せずに振る舞いを追加します—アドホック多相です。trait を定義し、implicit インスタンスを提供し、implicit パラメータを使用します。これが Ordering、Numeric、Show の動作方法です。継承より柔軟です。

scala
trait Show[A] { def show(a: A): String }
object Show {
  implicit val intShow: Show[Int] = (a: Int) => a.toString
  implicit val strShow: Show[String] = identity
}
def printIt[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))
printIt(42)       // 42
printIt("hello")  // hello
// Type class: ad-hoc polymorphism

拡張メソッド(Scala 2)

Implicit class は既存の型に拡張メソッドを追加します。単一パラメータを持つ implicit class を定義すると、そのメソッドがその型で利用可能になります。Int、String などにユーティリティメソッドを追加するために使用します。Scala 3 はよりクリーンな 'extension' 構文を使用します。

scala
implicit class IntOps(val n: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to n).foreach(_ => f)
  def squared: Int = n * n
}
5.times { print("hi") }  // hihihihihi
println(5.squared)  // 25
// Scala 3: extension (n: Int) def squared = n * n
10

並行性と Future

Future と Async

Future は非同期計算を表します。onComplete がコールバックを登録します。本番 Web サーバーではブロック(Await.result)しないでください—スレッドを占有します。for 内包表記で関数的に future をチェーンします。ExecutionContext が必要です。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
  Thread.sleep(1000)
  42
}
f.onComplete {
  case scala.util.Success(v) => println(s"Got ${v}")
  case scala.util.Failure(e) => println(s"Err: ${e}")
}
// Don't block in production—use callbacks or for-comprehensions

Future の合成

Future の for 内包表記は順次実行します(各々が前を待機)。並列実行には、まずすべての Future を開始し、Future.sequence で結合します。Future.traverse は map + sequence を1ステップで行います。これが非同期作業を合成する慣用的な方法です。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f1 = Future { 10 }
val f2 = Future { 20 }
val sum = for {
  a <- f1
  b <- f2
} yield a + b  // Future(30)
// Parallel execution
val results = Future.sequence(List(
  Future { 1 }, Future { 2 }, Future { 3 }
))
results.map(_.sum)  // Future(6)

並列コレクション

.par はコレクションを並列版に変換します—操作が自動的に複数スレッドを使用します。大きなコレクションの CPU バウンド作業に適しています。注意:非結合的演算(減算など)は異なる結果を与える可能性があります。I/O バウンド作業には不適切です。

scala
val nums = (1 to 1000000).toList
val sum = nums.par.sum  // parallel sum
println(sum)
val doubled = nums.par.map(_ * 2).toList
// .par converts to ParCollection
// Operations run on multiple threads
// Use for CPU-bound work on large collections

Promise(手動 Future)

Promise は Future の書き込み可能側です—success/failure で手動完了します。コールバックベースの API を Future にブリッジする場合や、複数の場所から Future を完了する場合に使用します。Future は読み取り専用、Promise は1回書き込みです。

scala
import scala.concurrent.{Promise, Future, ExecutionContext}
import ExecutionContext.Implicits.global
val p = Promise[Int]()
val f = p.future
// Complete the promise from another thread
Future { Thread.sleep(100); p.success(42) }
f.foreach(println)  // 42 (when complete)
// p.failure(new Exception) for errors
// Promise = write side, Future = read side

同期 vs 非同期(Await)

Await.result は Future が完了するまで現在のスレッドをブロックします(タイムアウト付き)。テストや main メソッドでのみ使用してください—非同期コードでブロックすると目的が失われます。本番ではコールバック(onComplete、map)または for 内包表記で非ブロッキングを維持してください。

scala
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
val f = Future { Thread.sleep(500); 42 }
// Block and wait (use sparingly—mainly in tests)
val result = Await.result(f, 1.second)
println(result)  // 42
// Await.ready returns Try, Await.result returns value
// Avoid in production servers—use callbacks instead
11

Implicit の深掘り

Implicit パラメータ

Implicit パラメータは、マッチする型の implicit 値がスコープにある場合、コンパイラが自動的に渡します。これにより「コンテキスト」パラメータ(ExecutionContext、ロギング、設定)のボイラープレートを削減します。コンパイラは検索します:ローカルスコープ、コンパニオンオブジェクト、implicit スコープ。オーバーライドするために明示的に渡すこともできます。過剰使用はコードを追跡しにくくします—真にコンテキスト的な依存関係に使用してください。

scala
import scala.concurrent.ExecutionContext

// Method with implicit parameter
def process[A](data: List[A])
    (implicit ec: ExecutionContext): Future[Unit] = {
  Future { data.foreach(println) }
}

// The compiler finds an implicit ExecutionContext in scope
implicit val ec: ExecutionContext = ExecutionContext.global
process(List(1, 2, 3))  // ec passed automatically

// Explicitly providing (overrides implicit)
process(List(1, 2, 3))(myCustomEC)

// Multiple implicit parameters
def log(msg: String)(implicit
    level: Level, logger: Logger): Unit = {
  logger.log(level, msg)
}

Implicit 変換

Implicit 変換は必要時に型間を自動的に変換します。implicit class(ゼロオーバーヘッドのために AnyVal を拡張)は既存の型に拡張メソッドを追加します—これが Scala が Int、String などにメソッドを追加する方法です。注意:implicit 変換はコードを混乱させる可能性があります(何が変換されているか?)。生の implicit def より拡張用に implicit class を優先してください。import scala.language.implicitConversions で有効化します。

scala
import scala.language.implicitConversions

// Implicit conversion: one type to another
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // intToString(42) called implicitly

// Extension via implicit class (Scala 2.10+)
implicit class RichInt(val self: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)
  def squared: Int = self * self
}

5.times { println("hi") }  // prints hi 5 times
3.squared  // 9

// Implicit conversion for type compatibility
implicit def javaToScalaList(jl: java.util.List[Int]): List[Int] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

val javaList: java.util.List[Int] = ???
val scalaList: List[Int] = javaList  // converted

Implicit 解決優先度

コンパイラは優先度で implicit を解決します:ローカルスコープ > コンパニオンオブジェクト > インポート > 継承。同じ型の2つの implicit が同等にスコープにある場合、「ambiguous implicit」エラーになります。LowPriorityImplicits trait パターンはより具体的な implicit でオーバーライド可能なデフォルトを提供します。解決順序の理解はライブラリ設計に不可欠です—ユーザーがオーバーライドできるようデフォルトを低優先度 trait に配置してください。

scala
// Priority of implicit resolution (highest to lowest):
// 1. Local implicit (defined in current scope)
implicit val ec1: ExecutionContext = ec1

// 2. Implicit in companion object
object MyService {
  implicit val ec2: ExecutionContext = ec2  // lower priority
}

// 3. Implicit scope (imported)
import somePackage.Implicits._

// 4. Implicit parameter default (inherited trait)
trait DefaultEc {
  implicit val ec: ExecutionContext = ExecutionContext.global
}

// More specific type wins
implicit def ord1: Ordering[Int] = ???
implicit def ord2: Ordering[Int] = ???  // ambiguous error!

// LowPriorityImplicits trait pattern
object MyLib {
  implicit val high: Ordering[Int] = ???
}
object MyLib extends LowPriorityImplicits
trait LowPriorityImplicits {
  implicit val low: Ordering[Int] = ???  // fallback
}

コンテキスト境界と Evidence

コンテキスト境界 [A: TypeClass] は implicit パラメータの糖衣構文です—implicit TypeClass[A] が存在することを表明します。implicitly[TypeClass[A]](Scala 2)または summon[TypeClass[A]](Scala 3)で取得します。コンテキスト境界は型クラス制約を読みやすくします:def sort[A: Ordering]。複数境界はスタックします:[A: Ordering: Numeric]。これが型クラス要件を表現する慣用的な方法です。

scala
// Context bound: [A: Ordering] means there's an implicit Ordering[A]
def max[A: Ordering](a: A, b: A): A = {
  val ord = implicitly[Ordering[A]]  // retrieve the implicit
  if (ord.gt(a, b)) a else b
}

// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
  if (ord.gt(a, b)) a else b

// summon (Scala 3) instead of implicitly
def max3[A: Ordering](a: A, b: A): A = {
  val ord = summon[Ordering[A]]
  if (ord.gt(a, b)) a else b
}

// Type class evidence
def sort[A: Ordering](list: List[A]): List[A] =
  list.sorted  // uses the implicit Ordering

// Multiple context bounds
def process[A: Ordering: Numeric](x: A, y: A): A = ???

Implicit スコープとコンパニオンオブジェクト

Implicit スコープは現在のスコープだけでなく、関連する型のコンパニオンオブジェクトも含みます。これが Ordering[Int] をインポートする必要がない理由です:Int のコンパニオンに存在します。このメカニズムにより型クラスが使いやすくなります:型のコンパニオンにインスタンスを定義すると自動的に利用可能になります。パッケージオブジェクトはパッケージの共有 implicit を保持します。この設計により「ゼロインポート」型クラス使用が可能になります。

scala
// Implicit in companion object is found automatically
case class UserId(value: Long)
object UserId {
  implicit val ordering: Ordering[UserId] =
    Ordering.by(_.value)
}

// No import needed—companion object implicits are in scope
List(UserId(3), UserId(1), UserId(2)).sorted
// Works because Ordering[UserId] is in UserId's companion

// Implicit scope includes:
// 1. Companion object of the type (UserId)
// 2. Companion object of the type class (Ordering)
// 3. Companion objects of type parameters

// This is why Int has an Ordering:
// object Int { implicit val ord: Ordering[Int] = ... }

// Package object for shared implicits
package object myapp {
  implicit val ec: ExecutionContext = ExecutionContext.global
  type Id = Long
}
12

型クラス

型クラスの定義

型クラスは型でパラメータ化された trait で、インスタンスが特定の型に振る舞いを提供します。継承と異なり、型クラスインスタンスを遡及的に追加できます(所有していない型にも)。Show[A] は A の表示方法を定義します。インスタンスはコンパニオンオブジェクトに存在します(自動 implicit スコープ)。これがアドホック多相です—型を変更せずに型ごとに異なる振る舞い。型クラスは Scala の最も強力な抽象化です。

scala
// Type class: a trait parameterized by type
trait Show[A] {
  def show(a: A): String
}

// Instances for specific types
object Show {
  // Instance for Int
  implicit val showInt: Show[Int] = (a: Int) => a.toString

  // Instance for String
  implicit val showString: Show[String] = (s: String) => s""$s""

  // Instance for List (recursive)
  implicit def showList[A](implicit s: Show[A]): Show[List[A]] =
    (list: List[A]) => list.map(s.show).mkString("[", ", ", "]")
}

// Usage with implicit parameter
def print[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))

print(42)           // 42
print("hello")      // "hello"
print(List(1, 2, 3))  // [1, 2, 3]

型クラスの使用(糖衣構文)

コンテキスト境界 [A: Show] + summon で型クラスインスタンスを取得します。拡張メソッド(implicit class)が .show のようなメソッドを追加し、型クラスを使用します。この組み合わせでクリーンな API を提供します:Show[Int] が存在すれば 42.show が動作します。標準ライブラリは多くの型クラスを提供します:Numeric、Ordering、Eq、Monoid(Cats)。構文(Numeric.Implicits._)をインポートすると + や sum のような演算子が追加され、型クラスを使用します。

scala
// Context bound syntax
def printAll[A: Show](items: List[A]): Unit =
  items.foreach(a => println(summon[Show[A]].show(a)))

// Extension methods via implicit class
implicit class ShowOps[A](val a: A) extends AnyVal {
  def show(implicit s: Show[A]): String = s.show(a)
}

42.show           // "42"
"hi".show         // ""hi""
List(1,2).show    // "[1, 2]"

// Combining: type class + extension methods
def format[A: Show](a: A): String = a.show

// Standard library type classes
def sum[A: Numeric](xs: List[A]): A =
  summon[Numeric[A]].plus(xs.head, xs.tail.foldLeft(
    summon[Numeric[A]].zero)(summon[Numeric[A]].plus))

// Or with syntax:
import Numeric.Implicits._
def sum2[A: Numeric](xs: List[A]): A = xs.sum

一般的な型クラス(Cats/Scalaz)

Cats と Scalaz が標準的な型クラスを提供します。Monoid(empty + combine)は汎用集約を可能にします。Functor(map)と Monad(pure + flatMap)はコンテナ(List、Option、Future、IO)を抽象化します。Eq は型安全な等価性を提供します(偶発的なクロスタイプ比較なし)。これらは合成します:Monad は Functor、Monoid は Semigroup。型クラスにより多くの型で動作する汎用的で再利用可能なコードを書けます。

scala
// Monoid: combine values with empty
trait Monoid[A] {
  def empty: A
  def combine(a: A, b: A): A
}
object Monoid {
  implicit val intAdd: Monoid[Int] = new Monoid[Int] {
    def empty = 0
    def combine(a: Int, b: Int) = a + b
  }
  implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
    def empty = Nil
    def combine(a: List[A], b: List[A]) = a ++ b
  }
}

// Functor: map over structure
trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

// Monad: chain operations
trait Monad[F[_]] {
  def pure[A](a: A): F[A]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

// Eq: type-safe equality
trait Eq[A] {
  def eqv(a: A, b: A): Boolean
}

// Semigroup: combine (no empty)
trait Semigroup[A] {
  def combine(a: A, b: A): A
}

型クラスの法則とテスト

型クラスの法則はインスタンスが満たすべき数学的性質です。Monoid は結合律と単位律を要求します。Functor は恒等と合成の保存を要求します。Cats のようなライブラリが法則定義を提供し、discipline + ScalaCheck が自動テストします。法則が型クラスを強力にする理由:汎用コード(foldMap など)が任意の法則遵守インスタンスで正しく動作します。インスタンスが法則を満たすことを常に検証してください—インスタンスのバグはそれを使用するすべての汎用コードを壊します。

scala
// Type class laws: properties that must hold
// Monoid laws:
// 1. Left identity:  combine(empty, a) == a
// 2. Right identity: combine(a, empty) == a
// 3. Associativity:  combine(a, combine(b, c)) == combine(combine(a, b), c)

// Functor laws:
// 1. Identity:    map(fa)(identity) == fa
// 2. Composition: map(fa)(f andThen g) == map(map(fa)(f))(g)

// Testing laws with ScalaCheck (discipline)
import org.scalacheck.Prop.forAll
import cats.kernel.laws.MonoidLaws

class MonoidSpec extends munit.FunSuite with Discipline {
  checkAll("Int Monoid", MonoidLaws[Int].monoid)
}

// Custom law test
def monoidLeftIdentity[A](implicit m: Monoid[A], arb: Arbitrary[A]) =
  forAll { (a: A) =>
    m.combine(m.empty, a) == a
  }

// Laws make type classes trustworthy:
// if an instance satisfies laws, generic code works correctly

型クラス導出(Scala 3)

Scala 3 は 'derives' キーワードと Mirror で型クラス導出を簡素化します。コンパイラが要素インスタンスを合成して case class と enum のインスタンスを自動生成できます。これによりすべての case class にインスタンスを書くボイラープレートを排除します(Scala 2 で shapeless で一般的)。Cats や Circe のようなライブラリが Scala 3 導出をサポートします。Mirror 型は汎用プログラミングのために型の構造(フィールド型、ラベル)へのコンパイル時アクセスを提供します。

scala
// Scala 3: derive type class instances automatically
import scala.deriving.Mirror

trait Show[A] {
  def show(a: A): String
}

object Show {
  // Inline given for derivation
  given showInt: Show[Int] with
    def show(a: Int) = a.toString

  given showString: Show[String] with
    def show(s: String) = s""$s""

  // Derive for products (case classes)
  given showProduct[A](using m: Mirror.ProductOf[A])
      (using ev: Show[m.MirroredElemTypes]): Show[A] with
    def show(a: A): String = ???

  // Or use Scala 3's derivation
  inline given derive[A](using m: Mirror.Of[A]): Show[A] = ???
}

// Auto-derive for case classes
case class Person(name: String, age: Int) derives Show
// Show[Person] is generated automatically
13

For 内包表記の深掘り

基本の For 内包表記

For 内包表記は flatMap/map/withFilter の糖衣構文です。各 <- は flatMap です(最後は map)。if ガードは withFilter になります。yield でコレクションを返し、省略すると命令型(foreach)になります。これは flatMap/map(Monad)を持つ任意の型で動作します:List、Option、Future、Try、IO。for 内包表記の習得は慣用的 Scala の鍵です—ネストした map/flatMap を読みやすいシーケンシャル構文で置き換えます。

scala
// For comprehension: syntactic sugar for flatMap/map
val result = for {
  x <- List(1, 2, 3)
  y <- List(10, 20)
} yield x + y
// List(11, 21, 12, 22, 13, 23)

// Desugared:
List(1, 2, 3).flatMap { x =>
  List(10, 20).map { y => x + y }
}

// With filters (if guards)
val evens = for {
  x <- 1 to 10
  if x % 2 == 0
} yield x
// Vector(2, 4, 6, 8, 10)

// Desugared:
(1 to 10).withFilter(_ % 2 == 0).map(identity)

// Without yield: imperative (foreach)
for (x <- 1 to 3) println(x)  // 1, 2, 3

Option と Future の For

For 内包表記は任意の Monad で動作します。Option では None でショートサーキットします(None を返します)。Future では失敗でショートサーキットします。これによりシーケンシャルな非同期/失敗する可能性のあるコードが関数型でありながら直線的な命令型コードのように読めます。各 <- 行は前のバインディングに依存できます。これはネストした flatMap 呼び出しよりはるかにクリーンです。同じ構文が Try、Either、IO、カスタムモナドで動作します。

scala
// Option: chain operations that might return None
def getUser(id: Int): Option[User] = ???
def getEmail(user: User): Option[String] = ???

val email: Option[String] = for {
  user <- getUser(42)
  email <- getEmail(user)
} yield email

// Desugared:
getUser(42).flatMap(user => getEmail(user).map(email => email))

// Future: chain async operations
val result: Future[Int] = for {
  user <- fetchUser(1)      // Future[User]
  posts <- fetchPosts(user) // Future[List[Post]]
} yield posts.size

// If any returns None/failed Future, the whole chain short-circuits
// This is the power of monadic composition

Either とエラー処理の For

Either は Scala の型付きエラー処理です。For 内包表記は Either をチェーンし、Left(エラー)でショートサーキットします。これが関数型エラー処理です—例外なし、エラーは値です。Left 型がエラーです(通常 String または sealed trait)。Either は Scala 2.12+ で右バイアスです(map/flatMap は Right で動作)。このパターンは try/catch を合成可能で型安全なエラー伝播で置き換えます。Cats の Validated はエラー蓄積の代替です。

scala
// Either for error handling
def parseAge(s: String): Either[String, Int] =
  s.toIntOption.toRight(s"not a number: $s")

def validateAge(age: Int): Either[String, Int] =
  if (age >= 0) Right(age) else Left(s"negative: $age")

val result: Either[String, Int] = for {
  age <- parseAge("30")     // Right(30)
  valid <- validateAge(age) // Right(30)
} yield valid

// With error in chain
val error: Either[String, Int] = for {
  age <- parseAge("abc")    // Left("not a number: abc")
  valid <- validateAge(age) // skipped!
} yield valid
// error == Left("not a number: abc")

// Scala 3: for-comprehensions work with Either directly
// (Scala 2 needed either.map(_.right) or withFilter)

脱糖とカスタムモナド

flatMap と map を持つ任意の型が for 内包表記をサポートします—これが Monad パターンです。これらのメソッドを型に定義して for 構文を有効にします。= (<- ではなく)はローカルバインディングを作成します(flatMap に脱糖されません)。脱糖の理解は複雑な内包表記のデバッグとカスタムモナドの実装に役立ちます。コンパイラは for 内包表記を flatMap/map/withFilter チェーンに変換します。これが for が List、Option、Future、IO などで一様に動作する理由です。

scala
// For-comprehensions require flatMap, map, withFilter
// Define your own monad to use for-comprehensions

case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
  def flatMap[B](f: A => Box[B]): Box[B] = f(value)
  def withFilter(p: A => Boolean): Box[A] =
    if (p(value)) this else throw new NoSuchElementException
}

val result = for {
  x <- Box(10)
  y <- Box(20)
  if x < y
} yield x + y
// Box(30)

// Desugaring steps:
// 1. Box(10).flatMap { x =>
// 2.   Box(20).withFilter(_ > x... wait, order matters
// Actual:
// Box(10).flatMap(x =>
//   Box(20).withFilter(y => x < y).map(y => x + y))

// Assignment within for (= instead of <-)
for {
  x <- List(1, 2, 3)
  doubled = x * 2  // local val
} yield doubled

For vs Map/FlatMap(いつ使うか)

2つ以上の依存操作には for 内包表記を使用してください—ネストした flatMap より読みやすいです。単一変換には map が明確です。フラット化には flatMap を直接。副作用(結果なし)には yield なしの for を使用します。for 内包表記は各ステップが前のステップに依存する場合(モナド的チェーン)に輝きます。非同期/エラー処理コードをシーケンシャルに読ませます。深くネストした for(5レベル以上)は避けてください—可読性のためにヘルパーを抽出してください。

scala
// For: sequential, dependent operations
val result = for {
  user <- fetchUser(id)
  profile <- fetchProfile(user.id)
  avatar <- fetchAvatar(profile.avatarId)
} yield avatar

// Equivalent with flatMap (harder to read):
fetchUser(id).flatMap(user =>
  fetchProfile(user.id).flatMap(profile =>
    fetchAvatar(profile.avatarId)))

// Map: single transformation (no chaining)
users.map(_.name)  // simple, use map

// FlatMap: when you need to chain but for is overkill
users.flatMap(_.posts)  // List[Post]

// For without yield: side effects
for (user <- users) {
  saveToDb(user)
  sendEmail(user)
}

// Guidelines:
// - 1 operation: use map/flatMap directly
// - 2+ dependent operations: use for-comprehension
// - Side effects: use for without yield
14

Scala 3: Given と Using

Given インスタンス(implicit val の置き換え)

Scala 3 は implicit val/def を 'given' で置き換えます。given はより明確で明示的です。匿名 given(given Type = ...)はコンパイラ生成名を持ちます。'given T with' は複数メソッドを持つインスタンスを定義します。条件付き given(given [A: Ordering]: Ordering[List[A]])は implicit def を置き換えます。コンパニオンオブジェクトの given は Scala 2 と同様に自動的に implicit スコープにあります。キーワード変更により 'implicit' の過負荷(Scala 2 で4つの意味があった)を削減します。

scala
// Scala 2: implicit val
// implicit val ec: ExecutionContext = ExecutionContext.global

// Scala 3: given
given ec: ExecutionContext = ExecutionContext.global

// Anonymous given (inferred name)
given ExecutionContext = ExecutionContext.global

// Given with 'with' for complex types
given Show[Int] with
  def show(a: Int): String = a.toString

// Given in companion object (automatic scope)
object MyType:
  given Ordering[MyType] = Ordering.by(_.id)

// Conditional given (like implicit def)
given [A: Ordering]: Ordering[List[A]] with
  def compare(a: List[A], b: List[A]): Int =
    a.zip(b).find((x, y) => x != y) match
      case Some((x, y)) => summon[Ordering[A]].compare(x, y)
      case None => a.length - b.length

Using 句(implicit パラメータの置き換え)

Scala 3 は implicit パラメータを 'using' で置き換えます。これにより 'implicit' の2つの意味(パラメータ vs 変換)を分離します。using 句は 'using' キーワードで明示的に渡せます。コンテキスト境界 [A: T] は同じままです。summon[T] が implicitly[T] を置き換えます(より明確な名前)。using キーワードによりコールサイトでコンテキストを提供する際に明らかになります。これは純粋に構文的な変更です—セマンティクスは implicit パラメータと同じです。

scala
// Scala 2: implicit parameter
// def process[A](data: List[A])(implicit ec: ExecutionContext): Unit

// Scala 3: using
def process[A](data: List[A])(using ec: ExecutionContext): Unit =
  data.foreach(println)

// Context bound (unchanged)
def sort[A: Ordering](list: List[A]): List[A] = list.sorted

// Multiple using clauses
def log(msg: String)(using level: Level, logger: Logger): Unit =
  logger.log(level, msg)

// summon replaces implicitly
def max[A: Ordering](a: A, b: A): A =
  val ord = summon[Ordering[A]]
  if ord.gt(a, b) then a else b

// Provide explicitly with 'using'
process(data)(using myEC)
log("hello")(using Level.INFO, myLogger)

拡張メソッド(Scala 3)

Scala 3 は implicit class を 'extension' で置き換えます—より明確で焦点を絞っています。extension (self: T) が T にメソッドを定義します。拡張はジェネリックにできます(extension [A])。using と組み合わせて型クラス構文(42.show)を提供します。拡張は単なるメソッド追加です—ラッパーオブジェクトを作成しません(Scala 2 では AnyVal でゼロオーバーヘッド、Scala 3 ではネイティブ)。これが Scala 3 で既存の型にメソッドを追加する慣用的な方法です。

scala
// Scala 2: implicit class
// implicit class RichInt(val self: Int) extends AnyVal {
//   def squared: Int = self * self
// }

// Scala 3: extension
extension (self: Int)
  def squared: Int = self * self
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)

5.squared   // 25
3.times { println("hi") }

// Extension on generic type
extension [A](self: List[A])
  def takeWhileInclusive(p: A => Boolean): List[A] = ???

// Extension with using (type class syntax)
extension [A](self: A) def show(using s: Show[A]): String = s.show(self)

42.show  // uses given Show[Int]

// Multiple extensions in one block
extension (s: String)
  def isBlank: Boolean = s.trim.isEmpty
  def words: List[String] = s.split(" ").toList

Enum と ADT(Scala 3)

Scala 3 の enum は ADT 用の sealed trait + case object を置き換えます。より簡潔でパラメータ、フィールド、メソッドをサポートします。Enum case はパラメータを持てます(case class のように)。パターンマッチングは網羅チェックされます。Enum はジェネリックにできます(Option[A])。これにより enum と ADT を1つの構造に統合します。開いた階層(拡張可能)には sealed trait + case class を引き続き使用してください。閉じた列挙/ADT には enum がよりクリーンです。

scala
// Scala 3 enum (replaces sealed trait + case objects)
enum Color:
  case Red, Green, Blue

enum HttpStatus(val code: Int):
  case Ok extends HttpStatus(200)
  case NotFound extends HttpStatus(404)
  case Error extends HttpStatus(500)

// Pattern matching (exhaustive)
def describe(c: Color): String = c match
  case Color.Red => "red"
  case Color.Green => "green"
  case Color.Blue => "blue"

// Access fields
HttpStatus.Ok.code  // 200

// Parameterized enum cases
enum Option[+A]:
  case Some(value: A)
  case None

// ADT with methods
enum Tree[+A]:
  case Leaf(value: A)
  case Node(left: Tree[A], right: Tree[A])

  def size: Int = this match
    case Leaf(_) => 1
    case Node(l, r) => l.size + r.size + 1

トップレベル定義とインデント

Scala 3 はトップレベル定義を許可します—すべてを object でラップする必要はありません。これによりファイル構造が簡素化されます(Python/Go のように)。新しい構文は中括弧と有意インデントの両方をサポートします(オプション)。if/then が括弧付きの if/else を置き換えます。match は中括弧なしで式として使用できます。これらの変更により Scala 3 は後方互換性を保ちながらよりアプローチしやすくなります。スタイルを混在できます—明確さを加える場所では中括弧、ノイズを削減する場所ではインデントを使用します。

scala
// Scala 3: top-level definitions (no class wrapper needed)
// File: MyMath.scala
def add(a: Int, b: Int): Int = a + b  // top-level function

val Pi: Double = 3.14159  // top-level val

type Id = Long  // top-level type alias

given Show[Int] = (a: Int) => a.toString  // top-level given

extension (i: Int) def squared: Int = i * i  // top-level extension

// Indentation-based syntax (optional, braces still work)
def factorial(n: Int): Int =
  if n <= 1 then 1
  else n * factorial(n - 1)

// Or with braces:
def factorial2(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial2(n - 1)
}

// if/then, match/case without braces
val sign = if x > 0 then 1 else -1
val desc = x match
  case 0 => "zero"
  case _ => "nonzero"
15

拡張メソッドと構文

Implicit Class(Scala 2)

implicit class(Scala 2.10+)は既存の型に拡張メソッドを追加します。AnyVal を拡張するとゼロアロケーションになります(コンパイラがラッパーを消去)。クラスは単一のコンストラクタパラメータ(拡張される型)を取らなければなりません。implicit class のメソッドは拡張された型で利用可能になります。これが Scala が Int、String などを豊かにする方法です。共有のために implicit class をパッケージオブジェクトやユーティリティオブジェクトに配置してください。Scala 3 では 'extension' を使用します。

scala
// Scala 2: implicit class for extension methods
import scala.language.implicitConversions

implicit class RichString(val s: String) extends AnyVal {
  def wordCount: Int = s.split("\\s+").length
  def slug: String = s.toLowerCase.replaceAll("[^a-z0-9]+", "-")
  def encrypt(key: Int): String = s.map(c => (c + key).toChar)
}

"Hello World".wordCount  // 2
"My Blog Post!".slug     // "my-blog-post-"
"abc".encrypt(1)         // "bcd"

// AnyVal avoids allocation (zero overhead)
// implicit class must be in a trait, class, or object

// For generic extensions
implicit class RichList[A](val list: List[A]) extends AnyVal {
  def middle: Option[A] = list.lift(list.length / 2)
}

List(1, 2, 3, 4, 5).middle  // Some(3)

実践的な拡張メソッド

拡張メソッドはコレクションや他の型にユーティリティを追加する標準的な方法です。distinctBy、chunked、tap が一般的な追加です。=== と =!= 演算子(Eq 型クラス経由)は型安全な等価性を提供します(== と異なりクロスタイプを許可)。拡張をオブジェクトにグループ化し、必要な場所でインポートします。これによりコア型をクリーンに保ちながらドメイン固有メソッドを許可します。Scala のコレクションライブラリ自体がこのパターンを広く使用しています。

scala
// Common pattern: add utility methods to collections
object CollectionExtensions {
  implicit class RichSeq[A](val seq: Seq[A]) extends AnyVal {
    def chunked(size: Int): Seq[Seq[A]] =
      seq.grouped(size).toSeq

    def distinctBy[B](f: A => B): Seq[A] =
      seq.groupBy(f).values.map(_.head).toSeq

    def tap(f: A => Unit): Seq[A] = {
      seq.foreach(f)
      seq
    }
  }
}

import CollectionExtensions._

List(1, 2, 3, 4, 5).chunked(2)  // List(List(1,2), List(3,4), List(5))
List("aa", "bb", "ab").distinctBy(_.head)  // List("aa", "ab")
List(1, 2, 3).tap(println)  // prints 1,2,3, returns List(1,2,3)

// Type class syntax via extension
implicit class EqOps[A](val a: A) extends AnyVal {
  def ===(b: A)(implicit eq: Eq[A]): Boolean = eq.eqv(a, b)
  def =!=(b: A)(implicit eq: Eq[A]): Boolean = !eq.eqv(a, b)
}

Implicit 変換(慎重に使用)

Implicit 変換は自動的に型を変換しますが、危険です—コードの振る舞いが非自明になります。Scala 2.10+ は明示的なオプトインを要求します(scala.language.implicitConversions)。型を変更する変換より(型を変更する)拡張メソッド(型にメソッドを追加する)を優先してください。正当な使用:Java 相互運用(コレクション型間の変換)、DSL 構築。コンパイラは implicit 変換について警告します—これらの警告を真剣に受け止めてください。Scala 3 では given Conversion[T, U] が明示的なメカニズムです。

scala
import scala.language.implicitConversions

// Implicit conversion between types
implicit def stringToInt(s: String): Int = s.toInt
val n: Int = "42"  // stringToInt("42")

// Dangerous: can cause surprising behavior
implicit def intToBoolean(n: Int): Boolean = n != 0
if (1) println("yes")  // works! (intToBoolean(1))

// Safer: use extension methods instead
extension (n: Int) def toBool: Boolean = n != 0
if (1.toBool) println("yes")

// When implicit conversions are appropriate:
// 1. Java interop (java.util.List <-> scala.List)
implicit def javaListToScala[A](jl: java.util.List[A]): List[A] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

// 2. Backward compatibility layers
// 3. DSL construction (use sparingly)

// Enable per-file: import scala.language.implicitConversions

型レベルプログラミング

型レベルプログラミングはコンパイラがチェックする型に情報をエンコードします。ファントム型(未使用の型パラメータ)が状態(Open/Closed)を追跡し、誤用を防ぎます(閉じたファイルを読めない)。ペアノ自然数が数値を型として表現します。これによりコンパイル時の正確性保証が可能になります—バグがコンパイルエラーになります。状態機械、測定単位、サイズ付きベクターで使用されます。強力ですが複雑です—安全性が型の複雑さに見合う場合に使用します。shapeless(Scala 2)のようなライブラリが高度な型レベルプログラミングを可能にします。

scala
// Phantom types: track state at type level
sealed trait State
trait Open extends State
trait Closed extends State

class File[S <: State] private (val path: String)

object File {
  def open(path: String): File[Open] = new File[Open](path)
}

def close[S <: Open](f: File[S]): File[Closed] =
  new File[Closed](f.path)  // unsafe cast internally

def read[S <: Open](f: File[S]): String = "data"
// def read[S <: Closed](f: File[S]): String  // compile error

val f = File.open("test.txt")
read(f)  // OK: f is File[Open]
val closed = close(f)
// read(closed)  // COMPILE ERROR: closed is File[Closed]

// Type-level natural numbers (Peano)
sealed trait Nat
trait Zero extends Nat
trait Succ[N <: Nat] extends Nat

// Compile-time list length check
type _0 = Zero
type _1 = Succ[_0]
type _2 = Succ[_1]

Opaque 型(Scala 3)

Opaque 型(Scala 3)はゼロオーバーヘッドの newtype を作成します—値クラス(AnyVal)と異なり、ボックス化されず、ランタイムでは真に基の型そのものです。型の区別はコンパイル時にのみ存在し、混同を防ぎます(Celsius vs Fahrenheit、UserId vs Long)。定義するオブジェクト内では型とその基の型は相互に変換可能ですが、外では区別されます。これが Haskell の 'newtype' パターンです—ランタイムコストなしのドメイン駆動型。ID、単位、ドメインプリミティブに使用します。

scala
// Scala 2: value classes for zero-overhead wrappers
// case class UserId(value: Long) extends AnyVal

// Scala 3: opaque types (true zero overhead, no boxing)
object Types:
  type UserId = Long
  object UserId:
    def apply(value: Long): UserId = value

  type Email = String
  object Email:
    def apply(s: String): Email =
      require(s.contains("@"), "invalid email")
      s

import Types.*
val id: UserId = UserId(42)  // just a Long at runtime
val email: Email = Email("[email protected]")

// UserId and Long are NOT interchangeable outside the object
// def wrong(x: Long): UserId = x  // ERROR
// But inside the object, they're the same

// Newtype pattern: domain types without overhead
type Celsius = Double
type Fahrenheit = Double
// Prevents mixing up Celsius and Fahrenheit
def toF(c: Celsius): Fahrenheit = c * 9 / 5 + 32
16

テスト(ScalaTest と ScalaCheck)

ScalaTest スタイル

ScalaTest は複数のスタイルを提供します。FunSuite が最もシンプルです(test("name") { ... })。FlatSpec は BDD スタイルです("A Stack" should "...")。Matchers が読みやすいアサーションを提供します(shouldBe、should contain、should throw)。一貫性のためにプロジェクトごとに1つのスタイルを選択してください。ユニットテストには FunSuite が人気、振る駆動テストには FlatSpec が適しています。すべてのスタイルが同じマッチャとライフサイクルフックをサポートします。スタイルは構文に影響し、機能には影響しません。

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

// FunSuite: simple test functions
class MyTest extends AnyFunSuite with Matchers {
  test("addition works") {
    1 + 1 should be (2)
    1 + 1 shouldBe 2
    List(1, 2, 3) should contain (2)
  }

  test("string operations") {
    "hello".length shouldBe 5
    "hello" should startWith ("he")
  }
}

// FlatSpec: BDD-style
import org.scalatest.flatspec.AnyFlatSpec
class StackSpec extends AnyFlatSpec with Matchers {
  "A Stack" should "pop values in LIFO order" in {
    val stack = Stack(1, 2, 3)
    stack.pop() shouldBe 3
  }

  it should "throw on empty pop" in {
    val stack = Stack()
    a [NoSuchElementException] shouldBe thrownBy(stack.pop())
  }
}

// WordSpec, FreeSpec, PropSpec also available

アサーションとマッチャ

マッチャが表現力豊かなアサーションを提供します。shouldBe/should be が等価性。contain、have size、have key がコレクション用。startWith/endWith/include が文字列用。thrownBy が例外用。比較用のカスタムマッチャ(be >、be <=)。'should' DSL は英語のように読め、テストを自己文書化します。複雑なアサーションにはカスタムマッチャまたはプレーンな assert(condition) を使用します。マッチャの過剰なチェーンは避けてください—賢さより可読性を。

scala
import org.scalatest.matchers.should.Matchers._

// Equality
result shouldBe 42
result should be (42)
result should equal (42)

// Collections
list should contain (5)
list should not contain (0)
list shouldBe empty
list should have size 5
list should contain inOrder (1, 2, 3)
map should contain key ("name")
map should contain value ("Alice")

// Strings
s should startWith ("Hello")
s should endWith ("world")
s should include ("lo")
s should fullyMatch regex "H.*d".r

// Exceptions
a [IOException] should be thrownBy riskyOp()
the [IOException] thrownBy riskyOp() should have message "fail"

// Custom matchers
result should be > 0
result should be <= 100

// Type checks
result shouldBe a [List[_]]
result should be an [IllegalArgumentException]

BeforeAndAfter とフィクスチャ

BeforeAndAfterEach が各テストの周りにセットアップ/ティアダウンを実行します。beforeEach/afterEach をオーバーライドします。リソース管理にはローンパターン(withDb { conn => ... })がよりクリーンです—try/finally でクリーンアップを保証し、リソースを明示的にします。ScalaTest はフィクスチャコンテキスト(FixtureContext)と trait 経由の共有フィクスチャもサポートします。可変の beforeEach 状態よりローンパターンまたはフィクスチャメソッドを優先してください—より関数型でテスト間の共有状態バグを回避します。

scala
import org.scalatest.BeforeAndAfterEach
import org.scalatest.funsuite.AnyFunSuite

class DbTest extends AnyFunSuite with BeforeAndAfterEach {
  var conn: Connection = _

  override def beforeEach(): Unit = {
    conn = DriverManager.getConnection("jdbc:h2:mem:test")
    conn.execute("CREATE TABLE users (id INT, name VARCHAR)")
  }

  override def afterEach(): Unit = {
    conn.close()
  }

  test("insert works") {
    conn.execute("INSERT INTO users VALUES (1, 'Alice')")
    val count = conn.query("SELECT COUNT(*) FROM users")
    count shouldBe 1
  }
}

// Fixture via loan pattern
class FixtureTest extends AnyFunSuite {
  def withDb(test: Connection => Unit): Unit = {
    val conn = DriverManager.getConnection("jdbc:h2:mem:test")
    try test(conn) finally conn.close()
  }

  test("query works") {
    withDb { conn =>
      conn.execute("INSERT ...")
      // assertions
    }
  }
}

プロパティベーステスト(ScalaCheck)

ScalaCheck はランダムなテスト入力を生成し、多くのケースでプロパティをテストします。forAll はデフォルトで100個のランダム入力でプロパティを実行します。whenever が入力をフィルタします。カスタム Gen 型が生成データを制約します(Gen.choose、Gen.nonEmptyListOf)。これにより例ベーステストで見逃すエッジケース(空リスト、負の数、大きな値)を捕捉します。テーブル駆動テスト(Table)は特定のケース用です。プロパティベーステストは純粋関数とデータ変換に強力です—常に真であるべきことを定義します。

scala
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalacheck.Prop.forAll

class ListSpec extends AnyFunSuite with ScalaCheckPropertyChecks {
  // Property: reversing twice = identity
  test("reverse twice is identity") {
    forAll { (xs: List[Int]) =>
      xs.reverse.reverse shouldBe xs
    }
  }

  // Property with conditions
  test("head of sorted list is min") {
    forAll { (xs: List[Int]) =>
      whenever(xs.nonEmpty) {
        xs.sorted.head shouldBe xs.min
      }
    }
  }

  // Custom generators
  import org.scalacheck.Gen
  val smallInt = Gen.choose(1, 100)
  val nonEmptyList = Gen.nonEmptyListOf(smallInt)

  test("custom generator") {
    forAll(nonEmptyList) { (xs: List[Int]) =>
      xs should not be empty
      xs.forall(_ >= 1) shouldBe true
    }
  }

  // Table-driven tests
  test("addition table") {
    val cases = Table(
      ("a", "b", "sum"),
      (1, 2, 3),
      (10, 20, 30),
      (-1, 1, 0)
    )
    forAll(cases) { (a, b, sum) =>
      a + b shouldBe sum
    }
  }
}

モックとテストダブル

Mockito(ScalaTestPlus 経由)がテストダブルを作成します。mock[T] がモックを作成し、when(...).thenReturn(...) がメソッドをスタブし、verify が相互作用をチェックします。モックを使用してテスト対象を依存関係(データベース、API)から分離します。引数マッチャ(argThat)が特定の引数を検証します。過剰にモックしないでください—すべてをモックしている場合は、実際のインテグレーションをテストしてください。複雑な依存関係にはモックよりフェイク(インメモリ実装)を優先してください—より堅牢で読みやすいです。

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._

class UserServiceSpec extends AnyFunSuite with MockitoSugar {
  test("getUser returns user from repo") {
    // Create mock
    val repo = mock[UserRepository]
    val user = User(1, "Alice")

    // Stub: when X then Y
    when(repo.findById(1)).thenReturn(Some(user))

    val service = new UserService(repo)
    val result = service.getUser(1)

    result shouldBe Some(user)

    // Verify: was X called?
    verify(repo).findById(1)
    verify(repo, never()).findById(2)
  }

  test("createUser saves to repo") {
    val repo = mock[UserRepository]
    val service = new UserService(repo)

    service.createUser("Bob")

    // Verify with argument matcher
    verify(repo).save(argThat((u: User) => u.name == "Bob"))
  }
}

// Stubbing exceptions
when(repo.findById(99)).thenThrow(new RuntimeException("not found"))
17

高階関数と FP

値としての関数

Scala では関数は値です—格納、渡す、返すことができます。(A, B) => C が関数型です。イータ展開(multiply _)がメソッドを関数値に変換します。高階関数(関数を取る/返す)が強力な抽象化を可能にします:map、filter、reduce は HOF です。関数合成(andThen、compose)がパイプラインを構築します。カリー化(adder(5) が関数を返す)が部分適用です。これが Scala の関数型プログラミングの基盤です。

scala
// Functions are first-class values
val add: (Int, Int) => Int = (a, b) => a + b
val square: Int => Int = x => x * x

// Apply
add(2, 3)  // 5
square(4)  // 16

// Method to function (eta-expansion)
def multiply(a: Int, b: Int): Int = a * b
val mul = multiply _  // or just 'multiply' in Scala 3
mul(3, 4)  // 12

// Higher-order: function taking function
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(square, 2)  // 16

// Higher-order: function returning function
def adder(n: Int): Int => Int = _ + n
val add5 = adder(5)
add5(10)  // 15

// Function composition
val f: Int => Int = _ + 1
val g: Int => Int = _ * 2
val h = f andThen g  // f then g: (x+1)*2
val h2 = f compose g  // g then f: (x*2)+1
h(3)  // 8
h2(3)  // 7

カリー化と部分適用

カリー化は関数を複数のパラメータリストに分割し、部分適用を可能にします(一部の引数を固定し、残りのための関数を取得)。これは型推論を助け(前のパラメータが後のものを制約)、設定可能な関数を作成します(withDb(config))。プレースホルダー _ が部分適用します:sum(1, _) が関数を作成します。複数パラメータリストが Scala のカリー化メカニズムです。一部の引数が「設定」で他が「データ」の場合にカリー化を使用します。

scala
// Curried function: multiple parameter lists
def add(a: Int)(b: Int): Int = a + b
val add5: Int => Int = add(5)  // partial application
add5(10)  // 15

// Multiple parameter lists for type inference
def map[A, B](list: List[A])(f: A => B): List[B] =
  list.map(f)
map(List(1, 2, 3))(x => x * 2)  // A, B inferred from first list

// Curried form (Function types)
val curriedAdd: Int => Int => Int = a => b => a + b
curriedAdd(5)(10)  // 15

// Uncurrying
def uncurriedAdd(a: Int, b: Int): Int = a + b

// Practical: configuration via currying
def withDb(config: DbConfig)(f: Connection => Result): Result = ???
val withMyDb = withDb(myConfig) _
withMyDb { conn => /* ... */ }

// Partial application with placeholder
val sum: (Int, Int) => Int = _ + _
val addOne: Int => Int = sum(1, _)

純粋関数と参照透過性

純粋関数は同じ入力に対して常に同じ出力を返し、副作用がありません。テスト、推論、並列化、合成が容易です。参照透過性とは、関数呼び出しをその結果で置き換えても振る舞いが変わらないことを意味します。Scala は純粋性を強制しませんが、Cats Effect(IO モナド)のようなライブラリで副作用を分離できます。純粋なコア + 縁での IO が一般的な FP アーキテクチャです:ビジネスロジックは純粋、I/O は IO でラップされます。

scala
// Pure function: same input → same output, no side effects
def pureAdd(a: Int, b: Int): Int = a + b  // pure

// Impure: depends on external state
var counter = 0
def impureAdd(a: Int): Int = { counter += 1; a + counter }  // impure

// Impure: side effect
def impurePrint(a: Int): Int = { println(a); a }  // side effect

// Referential transparency: can replace call with result
val x = pureAdd(2, 3)
val y = x + x  // same as pureAdd(2, 3) + pureAdd(2, 3)

// Benefits of purity:
// 1. Easy to test (no setup/teardown)
// 2. Easy to reason about (no hidden state)
// 3. Parallelizable (no shared state)
// 4. Memoizable (cache results)
// 5. Composable (predictable)

// IO monad for side effects (Cats Effect)
import cats.effect.IO
val program: IO[Unit] = IO.println("hello")
val mapped: IO[String] = IO.pure("world").map(_.toUpperCase)
// Side effects captured in IO, run at edge of program

不変データと永続コレクション

不変データ構造は変更時に新しいコピーを返し、効率のために内部で構造を共有します(永続データ構造)。Scala のコレクションはデフォルトで不変です。case class の copy が変更されたコピーを作成します。深い更新には Lens ライブラリ(Monocle)が合成可能なアクセサを提供します。不変性はバグのクラス全体(競合状態、予期しない変更)を排除し、コードの推論を容易にします。パフォーマンスコストは構造共有(O(n) ではなく O(log n))により許容可能なことが多いです。

scala
// Immutable: operations return new collections
val list1 = List(1, 2, 3)
val list2 = list1 :+ 4  // List(1, 2, 3, 4)
// list1 is unchanged: List(1, 2, 3)

// Persistent data structures: share structure (efficient)
val map1 = Map("a" -> 1, "b" -> 2)
val map2 = map1 + ("c" -> 3)  // shares structure with map1
// O(log n) due to structural sharing, not O(n) copy

// Case classes: copy for modification
case class User(name: String, age: Int)
val alice = User("Alice", 30)
val older = alice.copy(age = 31)  // new instance, alice unchanged

// Lens (Monocle library) for nested updates
import monocle.macros.GenLens
val ageLens = GenLens[User](_.age)
val updated = ageLens.modify(_ + 1)(alice)  // User("Alice", 31)

// Benefits:
// - No bugs from shared mutation
// - Easy to reason about (values don't change)
// - Free concurrency (no locks needed)
// - Undo/redo trivial (keep old versions)

再帰と末尾呼び出し

末尾再帰(再帰呼び出しが最後の操作)は Scala コンパイラによりループに最適化されます—スタック成長なし。@tailrec がコンパイル時にこれを検証します。アキュムレータパターン(蓄積結果を渡す)が非末尾再帰を末尾再帰に変換します。foldLeft/foldRight が一般的な再帰パターンをカプセル化します。深い非末尾再帰にはトランポリン(Cats)または fold での書き直しを使用します。明確さのために明示的再帰より fold を優先してください—末尾再帰で慣用的です。

scala
// Regular recursion (can stack overflow)
def factorial(n: Int): Int =
  if (n <= 1) 1 else n * factorial(n - 1)
factorial(10000)  // StackOverflowError!

// Tail recursion: compiler optimizes to loop
import scala.annotation.tailrec
@tailrec
def factorialTail(n: Int, acc: Int = 1): Int =
  if (n <= 1) acc else factorialTail(n - 1, n * acc)
factorialTail(10000)  // works (no stack overflow)

// @tailrec annotation: compiler verifies it's tail-recursive
// Error if not actually tail-recursive

// Trampoline for non-tail recursion
// (Cats' Trampoline or Free monads)
// Converts stack recursion to heap

// Fold as alternative to recursion
def sum(list: List[Int]): Int = list.foldLeft(0)(_ + _)
// foldLeft is tail-recursive internally

// Pattern: accumulator pattern
@tailrec
def reverse[A](list: List[A], acc: List[A] = Nil): List[A] =
  list match
    case Nil => acc
    case head :: tail => reverse(tail, head :: acc)
18

Case Class と ADT

Case Class の基礎

Case class は Scala の主要なデータモデリングツールです。コンパイラが equals、hashCode、toString、copy、apply、unapply(パターンマッチング用)、アクセサメソッドを生成します。デフォルトで不変です(val フィールド)。DTO、値オブジェクト、メッセージ、ドメインエンティティ(不変の場合)に使用します。コンパニオンオブジェクトの apply により 'new' なしで構築できます。copy が非破壊的更新を可能にします。Case class は ADT(代数的データ型)の基盤です。

scala
// Case class: immutable data holder with boilerplate generated
case class Person(name: String, age: Int)

val alice = Person("Alice", 30)
val bob = Person("Bob", 25)

// Auto-generated methods:
alice.toString  // "Person(Alice,30)"
alice == Person("Alice", 30)  // true (structural equality)
alice.hashCode  // based on fields

// Pattern matching
alice match
  case Person(name, age) => s"$name is $age"

// Copy with modifications
val older = alice.copy(age = 31)  // Person("Alice", 31)

// Companion object with apply (no 'new' needed)
val p = Person("Charlie", 40)  // Person.apply called

// Fields accessed directly
alice.name  // "Alice"
alice.age   // 30

// Case class with default values
case class Point(x: Double = 0, y: Double = 0)
Point()  // Point(0.0, 0.0)
Point(y = 5)  // Point(0.0, 5.0)

Sealed Trait と ADT

ADT(代数的データ型)はデータを閉じたケースの集合としてモデル化します(sealed trait + case class/object)。'sealed' はすべてのサブタイプが同じファイルにあることを意味し、網羅的パターンマッチングを可能にします—コンパイラがケースの欠落で警告します。新しいケースの追加は更新が必要なすべての場所を示します(安全なリファクタリング)。Case object はシングルトン(パラメータなし)です。Option、List、Either はすべて ADT です。これがドメインをモデル化する関数型の方法です—各バリアントがケース、振る舞いはパターンマッチングにあります。

scala
// ADT: sealed trait + case classes/objects
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(a: Double, b: Double, c: Double) extends Shape

// Exhaustive pattern matching (compiler checks all cases)
def area(shape: Shape): Double = shape match
  case Circle(r) => math.Pi * r * r
  case Rectangle(w, h) => w * h
  case Triangle(a, b, c) =>
    val s = (a + b + c) / 2
    math.sqrt(s * (s-a) * (s-b) * (s-c))

// Adding a new case: compiler warns about non-exhaustive matches
case class Square(side: Double) extends Shape
// area now needs a Square case!

// Case objects for singletons (no parameters)
sealed trait Status
case object Active extends Status
case object Inactive extends Status
case object Pending extends Status

// Option and List are ADTs:
// sealed trait Option[+A]
// case class Some[A](value: A) extends Option[A]
// case object None extends Option[Nothing]

Case Class の機能

Case class はメソッド、カスタム apply(検証用)、trait の実装、ジェネリクスを持てます。コンパニオンオブジェクトのカスタム apply は構築前に検証できます(不正な状態を表現不可能にする)。Case class は多相のために trait を実装できます。プライベートコンストラクタ(case class Email private)は検証用にコンパニオンの apply の使用を強制します。これによりデータモデリングとカプセル化を組み合わせます—サイレントに失敗しないコンストラクタです。

scala
// Case class with methods
case class Vec2(x: Double, y: Double) {
  def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)
  def magnitude: Double = math.sqrt(x*x + y*y)
  def dot(other: Vec2): Double = x*other.x + y*other.y
}

Vec2(1, 2) + Vec2(3, 4)  // Vec2(4.0, 6.0)
Vec2(3, 4).magnitude  // 5.0

// Case class with custom apply (validation)
case class Email private (value: String)
object Email {
  def apply(value: String): Email = {
    require(value.contains("@"), "invalid email")
    new Email(value)  // bypass public apply
  }
}
// Email("notanemail")  // IllegalArgumentException
Email("[email protected]")  // works

// Case class implementing trait
trait Jsonable {
  def toJson: String
}
case class User(name: String, age: Int) extends Jsonable {
  def toJson: String = s"""{"name":"$name","age":$age}"""
}

// Case class with type parameters
case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
}

パターンマッチングの深掘り

パターンマッチングは Scala の強力な分解ツールです。サポート:リテラルパターン(0、"hello")、型パターン(s: String)、case class パターン(User(name, age))、ガード(if condition)、ネストしたパターン、バインディング(@)。コンパイラは sealed 型の網羅性をチェックします。パターンは上から下に試行されます。@ を使用して部分を抽出しながら全体をバインドします。パターンマッチングは if-else チェーン、型チェック、分解を1つの統一された読みやすい構文で置き換えます。

scala
// Various pattern types
val x: Any = (1, "hello")

x match
  case (a: Int, b: String) => s"int $a, string $b"
  case (a, b) => s"pair: $a, $b"
  case _ => "other"

// Guards
def classify(n: Int): String = n match
  case n if n < 0 => "negative"
  case 0 => "zero"
  case n if n % 2 == 0 => "even positive"
  case _ => "odd positive"

// Case class patterns (nested)
case class Point(x: Int, y: Int)
case class Shape(center: Point, size: Int)

def isOrigin(s: Shape): Boolean = s match
  case Shape(Point(0, 0), _) => true
  case _ => false

// Named patterns (bind the whole while extracting)
case class User(name: String, age: Int)
def describe(u: User): String = u match
  case u @ User(name, age) if age < 18 => s"$name is a minor"
  case User(name, _) => s"$name is an adult"

// Type patterns
def handle(x: Any): String = x match
  case s: String => s"string: $s"
  case n: Int => s"int: $n"
  case list: List[_] => s"list of size ${list.size}"
  case None => "none"
  case _ => "unknown"

抽出子とカスタムパターン

抽出子(unapply)は任意の型でパターンマッチングを可能にし、case class だけではありません。これによりマッチングパターンをデータ表現から分離します。正規表現は抽出子です(グループがバインディングになります)。外部型(JSON、URL)の抽出子を変更せずに書けます。unapply は抽出用に Option[(T1, T2, ...)] を返すか、単純マッチング用に Boolean を返します。抽出子によりパターンマッチングが任意のデータソースに拡張可能になります。Case class は unapply を自動生成し、カスタム抽出子は非 case class 型にマッチングを追加します。

scala
// Custom extractor via unapply
object Email {
  def unapply(str: String): Option[(String, String)] = {
    val parts = str.split("@")
    if (parts.length == 2) Some((parts(0), parts(1)))
    else None
  }
}

// Use in pattern matching
"[email protected]" match
  case Email(user, domain) => s"user: $user, domain: $domain"
  case _ => "not an email"

// Boolean extractor (no extracted values)
object Even {
  def unapply(n: Int): Boolean = n % 2 == 0
}

5 match
  case Even() => "even"
  case _ => "odd"

// Extractor with variable arity
object Pair {
  def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t._1, t._2)
}

// Regex as extractor
val Date = "(\\d{4})-(\\d{2})-(\\d{2})".r
"2024-01-15" match
  case Date(year, month, day) => s"$year/$month/$day"
  case _ => "not a date"

// Practical: parse without case classes
object Json {
  def unapply(s: String): Option[Any] =
    scala.util.Try(ujson.read(s)).toOption
}
19

コレクションの深掘り

不変コレクション

Scala のコレクションはデフォルトで不変です。List はリンクリストです(ランダムアクセス O(n))。Vector は O(log n) アクセスのツリーです。Map と Set はハッシュベースです。操作は新しいコレクションを返し、効率のために構造を共有します。

scala
val list = List(1, 2, 3)
val vector = Vector(1, 2, 3)  // Fast random access
val set = Set(1, 2, 3)
val map = Map("a" -> 1, "b" -> 2)
// All immutable: operations return new collections
val updated = map + ("c" -> 3)  // New map

コレクション操作

map が要素を変換します。filter が選択します。reduce が結合します。grouped がチャンク化します。flatten がネストしたコレクションをマージします。flatMap がマップしてフラット化します。すべて新しいコレクションを返します。遅延コレクションは効率のために .view または .iterator を使用します。

scala
val nums = (1 to 10).toList
val doubled = nums.map(_ * 2)
val evens = nums.filter(_ % 2 == 0)
val sum = nums.reduce(_ + _)
val grouped = nums.grouped(3).toList  // List(List(1,2,3), List(4,5,6), ...)
val flat = List(List(1,2), List(3,4)).flatten  // List(1,2,3,4)

コレクションのパターンマッチング

パターンマッチングはコレクションで動作します。::(cons)が head と tail にマッチします。List(a, b, c) は正確に3要素にマッチします。Nil は空リストにマッチします。_ はワイルドカードです。解析と分解に有用です。網羅的マッチングがバグを防ぎます。

scala
val list = List(1, 2, 3)
list match {
    case head :: tail => println(s"Head: $head")
    case Nil => println("Empty")
}
List(1, 2, 3) match {
    case List(a, b, c) => println(s"$a, $b, $c")
    case _ => println("Other")
}

可変コレクション

可変コレクション(ArrayBuffer、mutable.Set、mutable.Map)はインプレースで変更します。頻繁な更新には高速ですがスレッドセーフではありません。パフォーマンスが重要で不変性が不要な場合に使用します。.toMap、.toSet で不変に変換します。

scala
import scala.collection.mutable
val buffer = mutable.ArrayBuffer(1, 2, 3)
buffer += 4  // Add element
buffer -= 2  // Remove element
buffer(0) = 10  // Update
val mset = mutable.Set(1, 2, 3)
val mmap = mutable.Map("a" -> 1)
mmap("b") = 2

遅延コレクション

view は遅延ビューを作成します:操作は強制されるまで遅延されます。大きなコレクションでのチェーン操作に有用です。LazyList(Scala 2.13+)は遅延シーケンスです。遅延評価で無限シーケンスが可能です。.toList、.toArray で強制します。

scala
val lazyView = (1 to 1000000).view.map(_ * 2).filter(_ > 100)
// No computation yet
val first = lazyView.head  // Only computes first
val result = lazyView.take(10).toList  // Only 10 elements processed
// LazyList (formerly Stream)
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
20

Implicit

Implicit パラメータ

Implicit パラメータはスコープにある場合自動的に渡されます。コンパイラがマッチする implicit 値を検索します。設定、型クラス、コンテキストに使用されます。明示的にオーバーライドできます。複数の implicit は異なる型でなければなりません。

scala
def connect(url: String)(implicit timeout: Int): Unit = {
    println(s"Connecting to $url with timeout $timeout")
}
implicit val defaultTimeout: Int = 5000
connect("http://example.com")  // Uses 5000
connect("http://example.com")(3000)  // Explicit override

Implicit 変換

Implicit 変換は自動的に型を変換します。危険な場合があります(予期しない変換)。implicit class が拡張メソッドを追加します。Scala 3 は明確さのために given/using と extension を使用します。生の変換より拡張メソッドを優先してください。

scala
import scala.language.implicitConversions
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // Converts via intToString
// Extension methods (Scala 3 preferred)
implicit class RichInt(val n: Int) extends AnyVal {
    def squared: Int = n * n
}
5.squared  // 25

型クラス

型クラスはアドホック多相のパターンです。trait が振る舞いを定義し、インスタンスが特定の型に実装を提供します。Implicit 解決がインスタンスを見つけます。継承より柔軟です。Cats と Shapeless で一般的です。

scala
trait Show[A] {
    def show(a: A): String
}
object Show {
    implicit val intShow: Show[Int] = (a: Int) => a.toString
    implicit val stringShow: Show[String] = (a: String) => a
    def apply[A](a: A)(implicit s: Show[A]): String = s.show(a)
}
Show(42)  // "42"
Show("hello")  // "hello"

コンテキスト境界

コンテキスト境界 [A: TypeClass] は implicit パラメータの糖衣構文です。型クラスインスタンスは implicitly で利用可能です。型クラス制約のよりクリーンな構文です。Ordering、Numeric、カスタム型クラスで一般的です。

scala
def max[A: Ordering](a: A, b: A): A = {
    val ord = implicitly[Ordering[A]]
    if (ord.gt(a, b)) a else b
}
// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
    if (ord.gt(a, b)) a else b

Given/Using(Scala 3)

Scala 3 は明確さのために implicit を given/using で置き換えます。given がインスタンスを定義し、using がパラメータを宣言します。extension が implicit class を置き換えます。より明示的で読みやすいです。マイグレーションツールが Scala 2 の implicit を変換します。

scala
// Scala 3 syntax
given defaultTimeout: Int = 5000
def connect(url: String)(using timeout: Int): Unit =
    println(s"Connecting with $timeout")
connect("http://example.com")  // Uses given
// Extension methods
extension (n: Int)
    def squared: Int = n * n
21

並行性

Future

Future は非同期計算を表します。onComplete が完了を処理します。map/flatMap が操作をチェーンします。ExecutionContext がスレッドを提供します。Future は不変で1回限りです。複数の future には for 内包表記を使用します。

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
    Thread.sleep(1000)
    42
}
f.onComplete {
    case Success(v) => println(v)
    case Failure(e) => println(e)
}
// Map/flatMap for chaining
val f2 = f.map(_ * 2).flatMap(x => Future(x + 1))

Future の for 内包表記

for 内包表記は flatMap/map に脱糖されます。シーケンシャル:各ステップが前を待機します。並列実行には、for の前に future を開始します。ネストしたコールバックよりはるかに読みやすいです。任意のモナド(Future、Option、List)で動作します。

scala
def getUser(id: Int): Future[User] = ...
def getOrders(user: User): Future[List[Order]] = ...
val result: Future[List[Order]] = for {
    user <- getUser(1)
    orders <- getOrders(user)
} yield orders
// Equivalent to flatMap/map chaining

並列 Future

for 内包表記の前に future を開始すると並列実行されます。Future.sequence が List[Future[T]] を Future[List[T]] に変換します。Future.traverse は map と sequence を1ステップで行います。zip が2つの future を結合します。すべて最遅の完了時に完了します。

scala
val f1 = Future { compute1() }
val f2 = Future { compute2() }
val combined: Future[(Int, Int)] = for {
    r1 <- f1
    r2 <- f2
} yield (r1, r2)
// Or: Future.sequence(List(f1, f2))
// Or: Future.traverse(list)(compute)

Actor(Akka)

Akka actor は状態をカプセル化しメッセージで通信します。共有する可変状態はありません。各 actor は一度に1つのメッセージを処理します。!(tell)がファイアアンドフォーゲットで送信します。?(ask)が Future を返します。スーパービジョンが障害を処理します。並行ステートフルシステムに理想的です。

scala
import akka.actor.*
class Counter extends Actor {
    var count = 0
    def receive = {
        case "inc" => count += 1
        case "get" => sender() ! count
    }
}
val system = ActorSystem("mySystem")
val counter = system.actorOf(Props[Counter], "counter")
counter ! "inc"
counter ! "get"

Cats Effect IO

Cats Effect IO は純粋な関数型 IO モナドです。参照透過です:IO(println("x")) は値です。for 内包表記で合成します。キャンセル可能でリソースセーフです。unsafeRunSync がプログラムの縁で実行します。より良いセマンティクスを持つ Future の代替です。

scala
import cats.effect.IO
val program: IO[Int] = for {
    _ <- IO(println("Start"))
    result <- IO.pure(42)
    _ <- IO(println(s"Got $result"))
} yield result
// Run at end of world
program.unsafeRunSync()
// Referentially transparent, cancellable
22

パターンマッチングの深掘り

Case Class

Case class は equals、hashCode、toString、apply/unapply を自動生成します。パターンマッチングに最適です。パターンマッチングがそれらを分解します。デフォルトで不変です。代数的データ型に使用します。copy() が変更されたコピーを作成します。

scala
case class Point(x: Int, y: Int)
val p = Point(1, 2)
p match {
    case Point(0, 0) => "origin"
    case Point(0, _) => "on y-axis"
    case Point(x, 0) => s"on x-axis at $x"
    case Point(x, y) => s"at ($x, $y)"
}

Sealed Trait

sealed trait はサブタイプを同じファイルに制限します。コンパイラがパターンマッチングの網羅性をチェックします。新しいサブタイプの追加はすべてのマッチで警告を引き起こします。閉じた型階層に理想的です。ADT のために case class と組み合わせます。

scala
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Square(side: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
    case Circle(r) => math.Pi * r * r
    case Square(s) => s * s
    case Rectangle(w, h) => w * h
}  // Compiler warns if not exhaustive

ガードと抽出子

ガード(if)がパターンに条件を追加します。カスタム抽出子(unapply)が任意の型でパターンマッチングを可能にします。unapply はマッチを示す Option を返します。抽出子はマッチングを型から分離します。DSL と解析に強力です。

scala
def classify(n: Int): String = n match {
    case x if x < 0 => "negative"
    case 0 => "zero"
    case x if x % 2 == 0 => "even"
    case _ => "odd"
}
// Custom extractor
object Even {
    def unapply(n: Int): Option[Int] =
        if (n % 2 == 0) Some(n / 2) else None
}
4 match { case Even(half) => s"half is $half" }

部分関数

部分関数は一部の入力に対してのみ定義されます。isDefinedAt がチェックします。collect は定義された場所のみに適用します。コールバックとルーティングに有用です。orElse が部分関数を結合します。lift が Option を返す全関数に変換します。

scala
val pf: PartialFunction[Int, String] = {
    case 1 => "one"
    case 2 => "two"
}
pf.isDefinedAt(1)  // true
pf.isDefinedAt(3)  // false
// Collect = filter + map
List(1, 2, 3, 1).collect(pf)  // List("one", "two", "one")

パターンマッチングの型

パターンマッチングは型で動作しますが、型消去がジェネリクスに影響します。List(a, b) は2要素リストにマッチします。List(_*) は任意のリストにマッチします。List[Int] のようなジェネリック型でのマッチングは避けてください(消去されます)。ランタイム型情報には型タグを使用します。

scala
def describe(x: Any): String = x match {
    case i: Int => s"Int: $i"
    case s: String => s"String: $s"
    case List(a, b) => s"Two-element list: $a, $b"
    case List(_*) => "List with elements"
    case Some(v) => s"Some: $v"
    case None => "None"
    case _ => "Unknown"
}
23

関数型プログラミング

高階関数

高階関数は関数を取るまたは返します。カリー化が複数引数関数を単一引数チェーンに分割します。部分適用が一部の引数を固定します。関数合成と再利用を可能にします。_ が部分適用関数を作成します。

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(_ + 3, 5)  // 11
// Currying
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _  // Partial application
add5(3)  // 8

Option と Either

Option はオプショナルな値を表します:Some または None。null を回避します。getOrElse がデフォルトを提供します。Either は成功(Right)または失敗(Left)を表します。予期されるエラーには例外より優れています。両方ともモナドです:map、flatMap、for 内包表記。

scala
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
find(1).getOrElse("Unknown")  // "Alice"
find(-1).getOrElse("Unknown")  // "Unknown"
// Either for error handling
def parse(s: String): Either[String, Int] =
    try Right(s.toInt)
    catch { case _: Exception => Left(s"Not a number: $s") }

関数合成

compose が関数を右から左にチェーンします(数学のように)。andThen が左から右にチェーンします(より読みやすい)。両方とも新しい関数を作成します。パイプライン構築に有用です。Scala では関数は第一級の値です。

scala
val addOne: Int => Int = _ + 1
val double: Int => Int = _ * 2
// Compose (right to left)
val f = addOne compose double  // double then addOne
f(3)  // 7
// AndThen (left to right)
val g = addOne andThen double  // addOne then double
g(3)  // 8

再帰と末尾再帰

末尾再帰はコンパイラによりループに最適化されます。再帰呼び出しが最後の操作でなければなりません。@tailrec アノテーションがコンパイル時にこれを検証します。アキュムレータパターンが状態を運びます。深い再帰のスタックオーバーフローを防ぎます。

scala
// Not tail-recursive: stack overflow for large n
def factorial(n: Int): Int =
    if (n <= 1) 1 else n * factorial(n - 1)
// Tail-recursive: optimized to loop
import scala.annotation.tailrec
@tailrec
def factorial(n: Int, acc: Int = 1): Int =
    if (n <= 1) acc else factorial(n - 1, n * acc)

モナド

モナドは flatMap と unit(pure/pure)を持ちます。コンテキスト付きで操作をチェーンします(Option: 不在、List: 非決定性、Future: 非同期)。for 内包表記は flatMap/map に脱糖されます。モナド則が正しい合成を保証します。Cats が Monad 型クラスを提供します。

scala
// Monad laws: left identity, right identity, associativity
// Option is a monad:
Some(5).flatMap(x => Some(x + 1))  // Some(6)
None.flatMap(x => Some(x + 1))  // None
// List is a monad:
List(1, 2).flatMap(x => List(x, x * 10))  // List(1, 10, 2, 20)
// for-comprehension is monadic sugar:
for {
    x <- Some(5)
    y <- Some(x + 1)
} yield y  // Some(6)
24

一般的な落とし穴

Null vs Option

Scala には Java 互換性のために null がありますが推奨されません。Option が不在を明示的に表現します。パターンマッチングが None の処理を強制します。Try の例外には .toOption を使用します。Scala コードで null を避けてください、Java 相互運用のために予約してください。

scala
// BAD: null
def find(id: Int): String =
    if (id > 0) "Alice" else null
// GOOD: Option
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
// Scala avoids null; use Option
// NullPointer exceptions are rare in idiomatic Scala

Var vs Val

val は不変(値)、var は可変(変数)です。より安全で予測可能なコードのために val を優先してください。可変状態は推論と並行性を複雑にします。ローカルなパフォーマンスや真に必要な場合にのみ var を使用します。コレクションはデフォルトで不変です。

scala
var x = 1  // Mutable
x = 2  // OK
val y = 1  // Immutable
// y = 2  // Error
// Prefer val for immutability
// Use var only when necessary
// Mutable state causes bugs in concurrent code

等価性

Scala の == は equals を呼び出します(値等価)、Java と異なります。eq が参照等価をチェックします。ne は eq の否定です。値比較には常に == を使用します。Case class は正しい equals を持ちます。カスタムクラスでは equals と hashCode をオーバーライドします。

scala
val a = List(1, 2)
val b = List(1, 2)
a == b  // true (value equality)
a eq b  // false (reference equality)
// In Java: a == b compares references
// In Scala: == calls equals (value)
// Use eq for reference equality (rarely needed)

Implicit の曖昧さ

同じ型の複数の implicit は曖昧さエラーを引き起こします。Implicit スコープをクリーンに保ってください。区別するために特定の型(newtype)を使用します。Scala 3 の given/using はより明確です。Implicit 変換は避けてください、驚くべき振る舞いを引き起こします。

scala
implicit val s1: String = "hello"
implicit val s2: String = "world"
def greet(implicit s: String) = println(s)
greet  // Error: ambiguous implicit values
// Fix: only one implicit in scope
// Or: pass explicitly
greet(s1)

名前呼び vs 値呼び

名前呼びパラメータ(=> T)は遅延評価され、使用ごとに評価されます。値呼びパラメータは呼び出し前に一度評価されます。名前呼びはカスタム制御構造(unless、while)を可能にします。複数回評価される可能性があります、必要なら lazy val でキャッシュしてください。

scala
// By-value: evaluated once
def log(msg: String) = println(msg)
log(expensiveComputation())  // Evaluated before call
// By-name: evaluated each use
def log(msg: => String) = println(msg)
log(expensiveComputation())  // Evaluated only if called
// Useful for lazy evaluation and control flow
def unless(cond: Boolean)(body: => Unit): Unit =
    if (!cond) body
25

Akka Actor

Actor システム

ActorSystem は actor のコンテナです。actorOf が名前付きで actor を作成します。!(tell)が非同期でメッセージを送信します。?(ask)が送信して Future を返します。Actor はパスで識別されます。terminate がシステムをグレースフルにシャットダウンします。

scala
import akka.actor.*
val system = ActorSystem("MySystem")
val actor = system.actorOf(Props[MyActor], "myActor")
actor ! "Hello"  // Fire-and-forget
val future = actor ? "Query"  // Ask pattern
system.terminate()

Actor ライフサイクル

preStart は作成時に、postStop は終了時に実行されます。preRestart/postRestart がスーパービジョンを処理します。actor は再起動前に停止し、新しく開始されます。内部状態は再起動で失われます。初期化に preStart を、クリーンアップに postStop を使用します。

scala
class MyActor extends Actor {
    override def preStart(): Unit = println("Starting")
    override def postStop(): Unit = println("Stopped")
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
        println("Restarting")
        super.preRestart(reason, message)
    }
    def receive = {
        case "ping" => sender() ! "pong"
    }
}

スーパービジョン

スーパービジョンは障害の処理方法を定義します。Resume: 同じ状態で継続。Restart: 新しい状態で再作成。Stop: 永続的に終了。Escalate: 親に処理を委譲。OneForOneStrategy は失敗した子のみに影響します。AllForOneStrategy はすべての子に影響します。

scala
class Supervisor extends Actor {
    override val supervisorStrategy = OneForOneStrategy() {
        case _: ArithmeticException => Resume  // Continue
        case _: NullPointerException => Restart  // Restart actor
        case _: Exception => Stop  // Stop actor
    }
    def receive = { case _ => }
}

ルーター

ルーターはメッセージを複数の actor に分散します。Pool がワーカーを作成・管理します。Group が既存の actor を使用します。戦略:RoundRobin、Random、SmallestMailbox、ConsistentHashing、ScatterGatherFirst。ルーターは作業を並列化してスループットを向上させます。

scala
// Pool router: creates workers
val router = system.actorOf(
    RoundRobinPool(5).props(Props[Worker]),
    "router"
)
router ! "work"  // Distributed to one of 5 workers
// Group router: uses existing actors
val group = system.actorOf(
    RoundRobinGroup(paths).props(),
    "group"
)

永続化

PersistentActor はイベントをジャーナルに保存します(イベントソーシング)。コマンドは検証されてからイベントとして永続化されます。再起動時、イベントは receiveRecover 経由で再生されます。状態はイベントから再構築されます。クラッシュからの復旧を可能にします。Akka Typed には EventSourcedBehavior を使用します。

scala
class Counter extends PersistentActor {
    var count = 0
    override def persistenceId = "counter-1"
    override def receiveCommand = {
        case "inc" => persist(Incremented) { event =>
            count += 1
        }
    }
    override def receiveRecover = {
        case Incremented => count += 1
    }
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.