基礎
変数と型
より安全で予測可能なコードのために var(可変)より val(不変)を優先してください。Scala は型を推論しますが、公開 API の可読性のために明示的なアノテーションが役立ちます。すべてがオブジェクトです—プリミティブはありません(Int、Boolean はクラスです)。
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"..." がエスケープシーケンスを無効にします。これらのプレフィックスにより文字列構築が型安全で読みやすくなります。
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 を優先してください。
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)が型を強制します—アップキャストや曖昧さ回避に有用です。
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 inferredUnit と Nothing
Unit は void のようなものです(値は () のみ)。Nothing はインスタンスのないボトム型です—戻らない関数(throw、無限ループ)に使用されます。Nothing はすべての型のサブタイプで、柔軟な型推論を可能にします。
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")文字列
一般的な文字列メソッド
Scala の文字列は Java の文字列に暗黙変換(StringOps)経由で追加メソッドを持つものです。ほとんどのメソッドは新しい文字列を返します(不変)。明確さと正確性のために手動ループの代わりにこれらを使用してください。
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、コードに埋め込まれたテンプレートに最適です。
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 を使用してください。ループ内での + の繰り返しは避けてください(多くの中間オブジェクトを作成します)。
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 を使用し、例外なしに関数的にエラーを処理します。
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 です。
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#データ構造
List と Seq
List は不変の片方向リンクリストです—head/prepend は O(1)、ランダムアクセスは O(n) です。ランダムアクセスには Vector を使用してください(実質 O(1))。+: が先頭に追加、:+ が末尾に追加します。スレッド安全性のため不変コレクションを優先してください。
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 でなければなりません。
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。
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 を排除します。
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 を使用してください。
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制御フロー
If / Else(式)
Scala では if/else は値を返す式です。これにより三項演算子が不要になります。両方のブランチは互換性のある型でなければなりません。簡潔な条件付き代入にこれを使用してください。
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 neededFor 内包表記
for 内包表記は反復し、フィルタ(if ガ ード)と変換(yield)が可能です。yield なしではループ、yield ありではコレクションを構築します。to は終端を含み、until は除外します。flatMap/map/filter チェーンと同等です。
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)、型マッチング、分解をサポートします。網羅的でなければなりません(コンパイラが未カバーのケースで警告します)。値を返す式です。
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 を使用してください。
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 immutabilityTry / Catch / Finally
try/catch/finally は Java に似ていますが catch でパターンマッチングを使用します。関数型エラー処理には Try を優先してください—例外を Success/Failure 値としてラップし、try/catch のボイラープレートなしで map/flatMap チェーンを可能にします。
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関数
メソッド定義
メソッドは def name(params): ReturnType = body を使用します。デフォルト パラメータと名前付き引数をサポートします。Unit 戻り値 = 副作用のみ。単一式の本体は中括弧を省略します。= は必須です(なしの場合は Unit を返すプロシージャになります)。
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 などに渡せます。
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 をキャプチャするクロージャを返します。これが関数型プログラミングの心臓部です。
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 で一般的です。
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 は初回アクセスまで初期化を遅延させます—高価な値やオプションの値に使用します。
// 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 }クラスと OOP
クラスとコンストラクタ
主コンストラクタのパラメータはクラスシグネチャにあります。val パラメータは不変フィールド(公開 getter)、var は可変になります。クラス本体がコンストラクタです。補助コンストラクタは def this(...) を使用し、別のコンストラクタを呼び出さなければなりません。
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) // trueCase Class
Case class は不変のデータクラスで、equals、hashCode、toString、copy、apply/unapply を持つコンパニオンオブジェクトが自動生成されます。データモデリングとパターンマッチングに使用します。'new' は不要です。Scala の ADT の基盤です。
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, companionTrait(実装付きインターフェース)
Trait は Java のインターフェースに似ていますが実装を持てます。クラスは複数の trait をミックスインできます(extends/with で)。Trait は振る舞いの多重継承を可能にします。共有インターフェース、ミックスイン、線形化による積み重ね可能な変更に使用します。
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 linearizationObject(シングルトン)
object はシングルトン(1つのインスタンス)を宣言します。コンパニオンオブジェクト(クラスと同名)は静的ライクなメソッド、ファクトリ(apply)、抽出子(unapply)を保持します。呼び出し元が 'new' を省略できるようファクトリメソッドに apply を使用してください。これが慣用的な 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 を使用します。クラスの単一継承です。
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コレクションと関数型
Map / Filter / Fold
map が変換、filter が選択、reduce/foldLeft が集約します。これらが関数型データ変換のコアです。foldLeft はシードを取り結合的です、reduce は非空を必要とします。明確さと不変性のためにループの代わりにこれらを使用してください。
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, 5FlatMap と For 内包表記
flatMap はマップとフラット化を1ステップで行います—ネストしたコレクションとモナド操作に不可欠です。for 内包表記は flatMap/map/filter チェーンの糖衣構文です。複雑なネスト変換に使用してください—より読みやすいです。
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 はコンパレータでソートします。これらは新しいコレクションを返します(不変)。データ分析、分類、順序付けに使用します。
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 をサポートし関数型エラー伝播を可能にします。
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)は無限シーケンスを可能にします—要素はオンデマンドで計算されます。
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(_ + _)パターンマッチング
Case Class のマッチング
Sealed trait + case class で代数的データ型(ADT)を構成します。コンパイラが網羅性をチェックします—ケースを追加するとマッチの更新が必要な場所で警告します。パターンマッチングは case class を直接分解します。これが慣用的な 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)がパターンにランタイムチェックを追加します。マッチングをより表現力豊かにします。順序が重要です—最初のマッチが勝ちます。単純なパターンで表現できない範囲、条件、複雑なロジックにガードを使用します。
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個以上の要素にマッチします。これらのパターンは再帰的リスト処理と構造的分解を可能にします。解析とツリートラバーサルに強力です。
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 チェックなしでエラー処理が自然に流れます。
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 を自動生成します。
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")
}ジェネリクスと Implicit
ジェネリッククラスとメソッド
ジェネリクス(型パラメータ [A])で型安全で再利用可能なコードを書きます。単一型には [A]、2つには [A, B] を使用します。型推論が通常型を推測します。ジェネリクスはランタイムで消去されます(JVM の制限)がコンパイル時にチェックされます。
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)は非推奨です—代わりにコンテキスト境界を使用してください。これらは型パラメータを制約します。
// 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 で宣言します。コンパイラは外側のスコープとコンパニオンオブジェクトを検索します。
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 の動作方法です。継承より柔軟です。
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' 構文を使用します。
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並行性と Future
Future と Async
Future は非同期計算を表します。onComplete がコールバックを登録します。本番 Web サーバーではブロック(Await.result)しないでください—スレッドを占有します。for 内包表記で関数的に future をチェーンします。ExecutionContext が必要です。
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-comprehensionsFuture の合成
Future の for 内包表記は順次実行します(各々が前を待機)。並列実行には、まずすべての Future を開始し、Future.sequence で結合します。Future.traverse は map + sequence を1ステップで行います。これが非同期作業を合成する慣用的な方法です。
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 バウンド作業には不適切です。
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 collectionsPromise(手動 Future)
Promise は Future の書き込み可能側です—success/failure で手動完了します。コールバックベースの API を Future にブリッジする場合や、複数の場所から Future を完了する場合に使用します。Future は読み取り専用、Promise は1回書き込みです。
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 内包表記で非ブロッキングを維持してください。
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 insteadImplicit の深掘り
Implicit パラメータ
Implicit パラメータは、マッチする型の implicit 値がスコープにある場合、コンパイラが自動的に渡します。これにより「コンテキスト」パラメータ(ExecutionContext、ロギング、設定)のボイラープレートを削減します。コンパイラは検索します:ローカルスコープ、コンパニオンオブジェクト、implicit スコープ。オーバーライドするために明示的に渡すこともできます。過剰使用はコードを追跡しにくくします—真にコンテキスト的な依存関係に使用してください。
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 で有効化します。
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 // convertedImplicit 解決優先度
コンパイラは優先度で implicit を解決します:ローカルスコープ > コンパニオンオブジェクト > インポート > 継承。同じ型の2つの implicit が同等にスコープにある場合、「ambiguous implicit」エラーになります。LowPriorityImplicits trait パターンはより具体的な implicit でオーバーライド可能なデフォルトを提供します。解決順序の理解はライブラリ設計に不可欠です—ユーザーがオーバーライドできるようデフォルトを低優先度 trait に配置してください。
// 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]。これが型クラス要件を表現する慣用的な方法です。
// 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 を保持します。この設計により「ゼロインポート」型クラス使用が可能になります。
// 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
}型クラス
型クラスの定義
型クラスは型でパラメータ化された trait で、インスタンスが特定の型に振る舞いを提供します。継承と異なり、型クラスインスタンスを遡及的に追加できます(所有していない型にも)。Show[A] は A の表示方法を定義します。インスタンスはコンパニオンオブジェクトに存在します(自動 implicit スコープ)。これがアドホック多相です—型を変更せずに型ごとに異なる振る舞い。型クラスは 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 のような演算子が追加され、型クラスを使用します。
// 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。型クラスにより多くの型で動作する汎用的で再利用可能なコードを書けます。
// 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 など)が任意の法則遵守インスタンスで正しく動作します。インスタンスが法則を満たすことを常に検証してください—インスタンスのバグはそれを使用するすべての汎用コードを壊します。
// 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 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 automaticallyFor 内包表記の深掘り
基本の For 内包表記
For 内包表記は flatMap/map/withFilter の糖衣構文です。各 <- は flatMap です(最後は map)。if ガードは withFilter になります。yield でコレクションを返し、省略すると命令型(foreach)になります。これは flatMap/map(Monad)を持つ任意の型で動作します:List、Option、Future、Try、IO。for 内包表記の習得は慣用的 Scala の鍵です—ネストした map/flatMap を読みやすいシーケンシャル構文で置き換えます。
// 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, 3Option と Future の For
For 内包表記は任意の Monad で動作します。Option では None でショートサーキットします(None を返します)。Future では失敗でショートサーキットします。これによりシーケンシャルな非同期/失敗する可能性のあるコードが関数型でありながら直線的な命令型コードのように読めます。各 <- 行は前のバインディングに依存できます。これはネストした flatMap 呼び出しよりはるかにクリーンです 。同じ構文が Try、Either、IO、カスタムモナドで動作します。
// 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 compositionEither とエラー処理の For
Either は Scala の型付きエラー処理です。For 内包表記は Either をチェーンし、Left(エラー)でショートサーキットします。これが関数型エラー処理です—例外なし、エラーは値です。Left 型がエラーです(通常 String または sealed trait)。Either は Scala 2.12+ で右バイアスです(map/flatMap は Right で動作)。このパターンは try/catch を合成可能で型安全なエラー伝播で置き換えます。Cats の Validated はエラー蓄積の代替です。