Scala
melangの各noteで他言語と比較した、Scalaの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
強力な型推論を持ち、OOPとFPを両立する。
val age: Int = 20val age: Int = 20
val name = "Alice" // 型推論で String
age match {
case a if a >= 20 => println("adult")
case _ => println("minor")
}`val`(不変)/`var`(可変)で束縛の可変性をキーワードで区別する。case classとパターンマッチにより代数的データ型複数の型を組み合わせて新しい型を作る手法。「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼び、Rustの`enum`やHaskellの`data`が代表例。各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。に近い表現ができ、JVM上で実務的なFPスタイルを実現している。
型注釈の位置
postfix構文。戻り値の型注釈は省略できるが、公開APIでは明示が強く推奨される。
val age: Int = 20val age: Int = 20
val name = "Alice" // 型推論でString
def greet(name: String) = s"Hello, $name" // 戻り値の型は省略できるが非推奨`name: Type`というpostfix構文。引数の型は必須だが、メソッドの戻り値型はコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が本体から推論でき、文法上は省略できる。ただし公開APIで省略すると意図しない型が推論されるリスクがあるため、スタイルガイドでは明示が推奨される——「書けること」と「書くべきこと」が分かれる例。
型変換
メソッド形式の変換に加え、Optionを返すパースを持つ。
val y = x.toLongval x: Int = 10
val y: Long = x.toLong
val big: Long = 3000000000L
val z: Int = big.toInt // 黙って切り捨てられる
val a: Option[Int] = "42".toIntOption // 失敗するとNone
val b: Int = "abc".toInt // NumberFormatExceptionKotlinと同様に`toLong`, `toInt`のメソッド形式で数値変換を書く。文字列のパースには例外を投げる`toInt`と`Option`を返す`toIntOption`があり、`Try`や`Either`で包んで失敗を値として扱うこともできる。
暗黙の型変換
拡大変換は暗黙で、ユーザー定義の暗黙変換も追加できる。
val x: Int = 10val x: Int = 10
val y: Long = x // 暗黙にLongへ拡大変換される
val z: Double = x // Int -> Double も暗黙変換されるJavaと同様、IntからLong、FloatからDoubleのような拡大変換は暗黙に行われる。さらに`implicit def`(Scala 3では`given`によるconversion)でユーザー定義の暗黙変換を追加できるが、`scala.language.implicitConversions`の明示的な有効化を要求するなど、濫用への注意喚起がされている。
Enum / Sum Type
enumはpayload付きのADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現にも使える。
enum Result:
case Success(user: User)
case Failure(message: String)enum Result:
case Success(user: User)
case Failure(message: String)
def describe(r: Result): String = r match
case Result.Success(user) => s"ok: $user"
case Result.Failure(message) => s"error: $message"
// sealedな階層の網羅漏れはコンパイラが検出するScala 3の`enum`はvariantごとに異なるフィールドを持てるADTAlgebraic Data Type(代数的データ型)の略。複数の型を組み合わせて新しい型を作る手法で、「AまたはB」のように取りうる形が閉じた集合になっているものをSum Type(直和型)と呼ぶ。Rustの`enum`やHaskellの`data`が代表例で、各variant(構成要素)が異なるフィールドを持てる点で、単純な整数の列挙とは異なる。表現に使え、sealedな階層への`match`は網羅性チェックの対象になる。ただし設定によっては警告どまりになる場合もある。
値の不変性
既定のコレクションは不変。ただし`var`や可変コレクションも使える。
val x = ... / var x = ...case class User(name: String, tags: List[String])
val a = User("alice", List("a"))
// a.name = "bob" // コンパイルエラー
val b = a.copy(name = "bob") // 新しい値を生成
val c = a.copy(tags = a.tags :+ "admin")
var n = 0
n += 1 // OK: varは再代入できる
import scala.collection.mutable
val buf = mutable.ListBuffer(1)
buf += 2 // OK: 可変コレクションも使える`val`・`case class`・不変コレクションが既定で、更新は`copy`や`:+`で新しい値を作って表す。一方で`var`と`scala.collection.mutable`も使えるため、不変は慣習であり、言語が強制するわけではない。
名前付き引数
名前付き引数と位置引数を併用でき、デフォルト引数と組み合わせて使われる。
def foo(width: Int, height: Int = 0): Unitdef foo(width: Int, height: Int = 0): Unit = {}
foo(width = 10, height = 20) // 名前付き
foo(10, height = 20) // 位置引数と併用可Kotlinと同様、名前付き引数と位置引数を併用でき、デフォルト引数と組み合わせて使うことが多い。
関数の引数
引数は不変。値渡しに加えて、名前渡し(=>)を選べる。
def f(x: Int) / def f(x: => Int)def a(n: Int) = n + n // 呼び出し時に一度だけ評価される
def b(n: => Int) = n + n // 名前渡し。使うたびに評価される
a({ println("x"); 1 }) // "x"は1回
b({ println("x"); 1 }) // "x"は2回引数は不変で、参照渡しは無い。`=>`による名前渡しは、コピーか参照かではなく「いつ評価するか」を選ぶ仕組みで、melangが扱う「コピーか共有か」とは別の軸。可変なコレクションを渡せば中身を変更できる点はJava・Kotlinと同じ。
if 文/式
`if`は式。`else`がなければ`Unit`を返し、`else if`もそのまま式として連なる。
val x = if (cond) a else bval x = if (cond) 1 else 2
// elseを省くとUnitになり、値としては使えない
val y = if (cond) 1 // 型は AnyVal(Int と Unit の共通型)`if`は式で、`else`を省略すると値は`Unit`になる。値として使うつもりで`else`を忘れても型が広がるだけでエラーにならない場合がある。Scala 3では波括弧を省略するインデント構文も使える。
I/O の明示
ライブラリの`IO`/`ZIO`型で表現できるが、言語コアは強制しない。
def findUser(id: UserId): IO[User]def findUser(id: UserId): IO[User] = IO { /* ... */ }
def getUser(id: UserId): User =
findUser(id).unsafeRunSync() // 言語としては呼べてしまう
def getUser(id: UserId): IO[User] =
findUser(id) // 呼び出し側もIOのまま扱う、というのが規約cats-effectやZIOの`IO`/`ZIO`型は、Haskellの`IO`と同じ考え方で副作用を値として表現し合成する。ただしScala言語自体はこれを強制しておらず、`println`のような素のメソッドをどの関数からでも直接呼べる。effectの分離はライブラリとチームの規約に依存する。
言語名
"scalable language"の略で、意味を持つ合成的な命名。
"scalable"(拡張可能)と"language"を組み合わせた名前で、設計目標(小規模から大規模まで同じ言語で書ける)をそのまま言語名に込めている。意味のある名前は覚えやすい反面、その意味に沿わない機能を追加しづらくなるという制約も生む。