背景
就職先の方からオススメされたので、入社前に一通りインプットしてみる。
1章 Scalaひとめぐり
1-6 はじめてのScalaプログラミング
事前にJDK(Amzaon Correto 8)をインストール
インストール方法については省略。
$ java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment Corretto-8.292.10.1 (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM Corretto-8.292.10.1 (build 25.292-b10, mixed mode)
$ javac -version
javac 1.8.0_292
REPLでHello World
Homebrewでscalaをインストールする。
$ brew install scala
...
==> Summary
🍺 /usr/local/Cellar/scala/2.13.6: 42 files, 23.8MB
REPLを立ち上げて、「Hello World!」をプリントする。
$ scala
Welcome to Scala 2.13.6 (OpenJDK 64-Bit Server VM, Java 16.0.1).
Type in expressions for evaluation. Or try :help.
scala> println("Hello World!")
Hello World!
:q
でREPLを終了する。
scala> :q
$
scala
コマンドにファイル名を渡すと、そのソースコードを解釈して即時実行できる。
$ echo 'println("Hello World!")' > hello.scala
$ scala hello.scala
Hello World!
scalacでコンパイルしてみる
コンパイルだけを実行するには、scalac
コマンドを使う。
だが、先ほど作ったhello.scala
ファイルをscalac
コマンドに渡してコンパイルすると、エラーになる。
$ scalac hello.scala
hello.scala:1: error: expected class or object definition
println("Hello World!")
^
1 error
この場合、scalac
でのコンパイル対象はクラス、オブジェクト、トレイトのいずれかである必要がある。
(これを コンパイル単位 と呼ぶ)
ここでは、実行可能なmainメソッドが必要なので、mainメソッドを持つobjectを定義するように書き直す。
Appを継承するとmainメソッドの定義を省略できるので、今回はその例を示す。
$ echo 'object Hello extends App {
println("Hello World!")
}' > hello.scala
$ scalac hello.scala
ちなみに、Appを継承せずに普通にmainメソッドを定義すると、以下のようになる。
object Hello {
def main(args: Array[(String]): Unit = {
println("Hello World!")
}
}
scalac
によって.classという拡張子のついたクラスファイルが作られる。
クラスファイルとは、JVMで実行可能なJavaバイトコードを含むファイルのこと。
以下のように、scala
コマンドでクラスファイルを実行できる。
なお、scala
コマンドでクラスファイルを実行する場合、定義されたオブジェクト名やクラス名を指定する(ファイル名ではなく)。
$ ls
Hello$.class hello.scala
Hello$delayedInit$body.class
Hello.class
$ scala Hello
Hello World!
scala hello.scala
の実行は都度コンパイルしてから実行するので比較的遅いが、事前にコンパイルしたものを実行すると速い。
値や変数を定義する
val
で値を定義するときにlazy
というキーワードをつけると、その初期化を初回のアクセス時まで遅延できる。
scala> lazy val lazyDate = new java.util.Date
lazy val lazyDate: java.util.Date // unevaluated
scala> val date = new java.util.Date
val date: java.util.Date = Mon Jul 12 10:59:07 JST 2021
scala> lazyDate
val res0: java.util.Date = Mon Jul 12 10:59:38 JST 2021
scala> lazyDate
val res1: java.util.Date = Mon Jul 12 10:59:38 JST 2021
Scalaにおける一般的な命名規則
Scalaでは基本的に、大文字によって区切るキャメルケースが好まれる。
また、定義については大文字始まりのキャメルケースで定義する傾向がある。
// パッケージ名は . 区切りで階層を表現する
// Javaと同様domain名を逆にするケースが多いが、必ずしもそうである必要はない
// また、 - は使えないので、代わりに _ を使う
package jp.co.example.something_important
// クラス、オブジェクト、トレイト名は大文字で始めるキャメルケースで定義する
// フィールド名などは小文字始まりのキャメルケースで定義する
class MyClass(val myNumber: String) {
// インデントは半角スペース2個
// 定数は大文字始まりのキャメルケースで定義する(Scala公式サイトでの規約)
val DefaultNumber = 42
// メソッド名も小文字始まりでキャメルケース
def printSomething(): Unit = println("something")
}
関数オブジェクトを作る
Scalaでは厳密にはメソッドと関数は別の意味を持つ。
メソッドはdef
キーワードを使って以下のように定義する。
scala> def isAlphanumeric(str: String): Boolean = str.matches("[a-zA-Z0-9\\s]+")
def isAlphanumeric(str: String): Boolean
scala> isAlphanumeric("Amazon EC2")
val res2: Boolean = true
scala> isAlphanumeric("日本語")
val res3: Boolean = false
このメソッドから関数オブジェクトを生成できる。
以下のようにメソッド名と _ を続けると、関数オブジェクトとして値が返る。
scala> val isAlphanumericF = isAlphanumeric _
val isAlphanumericF: String => Boolean = $Lambda$1097/0x0000000801086538@61d6c8c4
関数オブジェクトは、関数リテラルやFunction0
~Function22
型のオブジェクトを生成することで得られる。
まず、以下は関数リテラルによる生成のコード例。
scala> val isAlphanumericF = (str: String) => str.matches("[a-zA-Z0-9\\s]+")
val isAlphanumericF: String => Boolean = $Lambda$1100/0x0000000801087358@f9cd1e6
以下は関数リテラルではなくFunction1
インスタンスを直接生成しているコード例。
Function1
の「1」は関数の引数が1つであることを示す。
中で定義されているapply
メソッドは、Function1
型のオブジェクトが持つ唯一のメソッドで、関数オブジェクトが実行されるとき、このメソッドが呼ばれる。
こうしてみると、「関数オブジェクト」と言っても、見慣れた普通のオブジェクトであることがわかる。
scala> val isAlphamericF = new Function1[String, Boolean] {
| def apply(str: String) = str.matches("[a-zA-Z0-9\\s]+")
| }
val isAlphamericF: String => Boolean = <function1>
このようにして生成した関数オブジェクトは他の関数や引数として渡せすことができる。
以下の例にあるようなSeq[String]
のfilter
メソッドは、String => Boolean
の関数を引数に取る。
先に定義したisAlphamericF
をそのまま渡すことができる。
scala> val words = Seq("Scala", "2.12")
val words: Seq[String] = List(Scala, 2.12)
scala> val alphamericWords = words.filter(isAlphamericF)
val alphamericWords: Seq[String] = List(Scala)
1-8 ビルドツールの利用
sbt
Scalaにはsbtという広く利用されているビルドツールがある。
sbtは基本的にはScalaプログラムのコンパイルおよびパッケージビルドを行うためのツールだが、それだけでなくタスクの実行もできるだので、テストの実行などプログラム開発に必要な作業はすべてsbtでできるようになっている。
また、sbtプラグインを実装すれば、誰でも拡張できるようにもなっている。
sbtはLightbend社に所属するエンジニアがSalaコアチームと連携しながらフルタイムワークで精力的に開発しているツール。
最もScalaのビルドに最適化されたビルドツールと言える。
REPLもsbtではconsole
というコマンドで起動できる。
REPLを起動するためにscalaコマンドを入れている場合は、実はsbtさえあればscala
コマンドがインストールされていなくても構わない。
scala
コマンドと違ってsbtでREPLを起動する場合は、利用するScalaのバージョンを切り替えることも簡単にできる。
以下はset scalaVersion := {version}
でScalaのバージョンを切り替えてconsole
を立ち上げ直しているコマンド例。
$ sbt
[info] Updated file /Users/glaciermelt/environment/test_0712/project/build.properties: set sbt.version to 1.5.4
[info] welcome to sbt 1.5.4 (Homebrew Java 16.0.1)
[info] loading project definition from /Users/glaciermelt/environment/test_0712/project
[info] set current project to test_0712 (in build file:/Users/xxx/environment/test_0712/)
[info] sbt server started at local:///Users/xxx/.sbt/1.0/server/54048a0bbc2aa2ba2be2/sock
[info] started sbt server
sbt:test_0712> scalaVersion
[info] 2.12.14
sbt:test_0712> console
https://repo1.maven.org/maven2/org/scala-sbt/compiler-bridge_2.12/1.5.5/compiler-bridge…
100.0% [##########] 52.2 KiB (34.9 KiB / s)
[info] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.14. Compiling...
[info] Compilation completed in 7.626s.
[info] Starting scala interpreter...
Welcome to Scala 2.12.14 (OpenJDK 64-Bit Server VM, Java 16.0.1).
Type in expressions for evaluation. Or try :help.
scala> :q
[success] Total time: 85 s (01:25), completed 2021/07/12 15:39:41
このようにsbtなどのScalaのビルドツールとJDKさえあれば、Scalaのプログラムのコンパイルと実行、パッケージングだ毛でなく、REPLの実行まで必要な作業はすべて可能。
scala/scalac
がインストールされていなくても良いという理由がご理解いただけただろうか?
2章 Scalaの基礎
~2.3まで確認済み。
続き:2.4~