Open14

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、可変変数がvarlet let mut なRustよりも可変を許容しそう感があるけれど実際Rustのほうがmutable使うと思う。
  • val n: Double = 3 のような型指定ももちろんできる。いつからかわからないが、型名は後置するのが流行りらしい。
  • Scala研修テキストの練習問題 が普通に難しい。どこが難しいかというと社会人みのある問題設定に戸惑う

¥3,950,000を年利率2.3%の単利で8か月間借り入れた場合の利息はいくらか(円未満切り捨て)

年利は0.023なので3950000を1年借り入れた場合の利息val x = 0.023 * 3950000、そのうち8か月 (\frac{2}{3}年)ぶんなので val y = x * (2d / 3)[1]。最後にy.asInstanceOf[Int]すると60566

脚注
  1. 数式上ではかっこは省けるが先に2倍するとオーバーフローかましそう ↩︎

定価¥1,980,000の商品を値引きして販売したところ、原価1.6%にあたる¥26,400の損失となった。割引額は定価の何パーセントであったか

損失264000.016で割ると原価1650000が求まる。26400の損失なのだから割引後の価格は1650000 - 264001623600。割引額は定価との差なので1980000 - 1623600356400。これを定価で割って100倍する 356400 / 1980000d * 10018.0%。

古い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())

クラスと同名のシングルトンオブジェクト (コンパニオンオブジェクト) を定義してユーティリティ関数を作ったりプライマリコンストラクタを隠蔽したりできる。

User.scala
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.scala
@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
ログインするとコメントできます