Scala やる
Scalaというオブジェクト指向と関数型を合体させたプログラミング言語があるらしい。Rustにも影響を与えているらしいのでどういう言語なのか試してみる。
JavaバイトコードにコンパイルされてJVMで動く。コンパイラ (scalac
) を直接触らずビルドツール (sbt
) を主に使うところがRustっぽい。Nixでさくりと入れる
{
description = "Scalaの学習メモ";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages.${system}; in
rec {
devShell = pkgs.mkShell {
nativeBuildInputs = [ pkgs.sbt pkgs.adoptopenjdk-bin ];
};
}
);
}
sbt console
でREPLが起動する。RustにもREPLがほしいと思うことはある。REPL触った所感
- 毎回の評価結果が
res1
res2
という感じで参照できる - 数値型はInt/Long, Float/Doubleがあるっぽい
- IntとDoubleが既定で、I/L/F/Dを後置するとその型になる
- Rustの
123i8
みたいなやつのほうが一貫性がある気もする
ながい型変換がある。メソッドっぽい
scala> (3.14).asInstanceOf[Int]
res39: Int = 3
- 不変変数が
val
、可変変数がvar
。let
let mut
なRustよりも可変を許容しそう感があるけれど実際Rustのほうがmutable使うと思う。 -
val n: Double = 3
のような型指定ももちろんできる。いつからかわからないが、型名は後置するのが流行りらしい。 - Scala研修テキストの練習問題 が普通に難しい。どこが難しいかというと社会人みのある問題設定に戸惑う
古いHello worldがこれ。
object HelloWorld {
def main(args: Array[String]): Unit = {
println("Hello, world!")
}
}
Scala 3ではこのように書く。def
によるメソッド定義がトップレベルで書けるようになった。
@main def hello : Unit =
println("Hello, world!")
ブロックはインデントで表す。最後の式がブロック全体の値になる。
val c =
println("a")
println("b")
3
@main def m: Unit =
println(c)
ifは式。古い構文はRustっぽいが条件式のparenが省略できない。
def adult(age: Int) = {
if (age < 18) {
"18歳未満"
} else {
"18歳以上"
}
}
条件式のparenが省略できないのは右に任意の式が書けるからだ。Rustは逆にブロックの役割を果たすbrace {}
が省略できない。
def adult(age: Int) = {
if (age < 18) "18歳未満"
else "18歳以上"
}
Scala 3では then
を挟むことで条件式のかっこを消せる。Haskellライク。
def isAdult(age: Int) =
if age < 18 then "18歳未満"
else "18歳以上"
else
節を省略すると else ()
が補われる。
def printIfAdult(age: Int) =
if age >= 18 then println("18歳以上")
パターンマッチは value match
という珍しい形式。
def show(i: Int) =
i match
case 0 => "zero"
case 1 => "one"
case _ => "other"
@main def main() =
println(show(3))
println(show(1))
println(show(0))
orパターン。
def isVowel(char: Char) =
char match
case 'a' | 'e' | 'i' | 'o' | 'u' => true
case _ => false
Listのunconsもある。
def printFirstTwo[T](list: List[T]) =
list match
case a :: b :: _ =>
println("a = " + a)
println("b = " + b)
case _ => list
@main def main() =
printFirstTwo(List(1, 2, 3))
型に対するマッチも可能。TypeScriptっぽい。
@main def main() =
val obj: Any = "String Literal"
obj match
case v: Int => println("integer!")
case v: String => println(v.toUpperCase())
クラスがある。Rustの struct Point(int, int)
みたいだ。
// プライマリコンストラクタの引数xとyをフィールドとしても公開する
class Point(val x: Int, val y: Int)
クラス内でメソッド定義する。
class Point(val x: Int, val y: Int):
def +(other: Point): Point =
Point(x + other.x, y + other.y)
// クラス `Any` のメソッド `toString` をオーバーライド
override def toString(): String =
"(" + x + ", " + y + ")"
@main def main() =
val p1 = Point(1, 3)
val p2 = Point(2, 3)
// 記号メソッドを中置演算子として使える
val p3 = p1 + p2
println(p3.toString())
メソッドはカリー化定義できる。部分適用すると関数になる。
class Adder:
def add(x: Int)(y: Int): Int = x + y
@main def main() =
val adder = Adder()
val add1 = adder.add(1) _
println(add1(2).toString())
クラスと同名のシングルトンオブジェクト (コンパニオンオブジェクト) を定義してユーティリティ関数を作ったりプライマリコンストラクタを隠蔽したりできる。
class User(val name: String, val id: Int):
infix def meets(other: User) =
println(this.name + " meets " + other.name)
object User:
var count = 0
def apply(name: String) =
count += 1
new User(name, count)
@main def main(): Unit =
val u1 = User("Makima")
val u2 = User("Denji")
u1 meets u2
型パラメータはbracketで指定する。
@main def main(): Unit =
val box = Box(2)
println(box.get())
class Box[A](private val value: A):
def get(): A =
value
型パラメータに +
をつけないと非変、つけると共変になる。
@main def main(): Unit =
// Box[Int] に Box[Int] を代入してもよい
val box: Box[Int] = Box[Int](2)
// Int => Any だが型パラメータ A が invariant なので Box[Int] => Box[Any] **ではない**
val box2: Box[Any] = box
共変の例。
@main def main(): Unit =
// Box[Int] に Box[Int] を代入してもよい
val box: BoxCovariant[Int] = BoxCovariant[Int](2)
// 型パラメータ A が covariant なので Box[Int] => Box[Any] **である**
val box2: BoxCovariant[Any] = box
class BoxCovariant[+A](private val value: A):
def get(): A =
value