コンテンツにスキップ

Scala

出典: フリー教科書『ウィキブックス(Wikibooks)』
Wikipedia
Wikipedia
ウィキペディアScalaの記事があります。

Scalaは、静的型付けのオブジェクト指向プログラミング言語であり、関数型プログラミングの機能も備えています。Scalaは、Java仮想マシン(JVM)上で動作するため、Javaのライブラリとの互換性があります。また、Scalaは、Javaに比べてシンプルな構文を持ち、プログラミングの生産性を高めることができます。

本書では、Scalaの基本的な構文や概念、そしてよく使われるライブラリやフレームワークについて学ぶことができます。具体的には、以下の内容が含まれます。

  1. Scalaの基本的な構文
    • 変数や定数の定義
    • 制御フロー(if文、for文、while文)
    • 関数の定義と呼び出し
  2. オブジェクト指向プログラミング
    • クラスとオブジェクトの定義
    • 継承、ポリモーフィズム、カプセル化
    • ケースクラスやトレイトの使い方
  3. 関数型プログラミング
    • 関数型プログラミングの基本的な概念
    • 高階関数、無名関数、クロージャーの定義と使い方
    • パターンマッチングの使い方
  4. ライブラリやフレームワーク
    • AkkaやSparkなどの分散処理ライブラリ
    • Play FrameworkやAkka HTTPなどのWebアプリケーションフレームワーク

本書を読むことで、Scalaの基本的な構文やオブジェクト指向プログラミング、関数型プログラミングの概念を理解し、実際にライブラリやフレームワークを使ってアプリケーションを開発することができるようになるでしょう。また、Scalaの特徴や利点を理解し、Javaに比べた生産性の向上を体感することができます。

はじめに

[編集]

Hello, World!

[編集]

他の多くのチュートリアルがそうであるように、私たちもまずはScalaの世界にあいさつすることから始めましょう。 hello.scalaというファイルを作り(Scalaではソースファイルに.scalaという拡張子を付けることが通例となっています)、次のように書いて保存して下さい。

hello.scala
object Main extends App{
    println("Hello world!")
}

環境

[編集]
  • オンライン開発実行環境
  • Javaとscalaのインストールとバージョン確認

コメント

[編集]

Scalaには、一般的なプログラミング言語と同様に、コード内にコメントを挿入する機能があります。Scalaのコメントは、Javaと同様に、複数行コメントや単一行コメントの形式をサポートしています。以下に、Scalaでのコメントの例を示します。

// これは単一行コメントです。

/*
   これは
   複数行コメントです。
*/

/** 
  * これはドキュメンテーションコメントです。
  * ScalaDoc形式のコメントで、関数やクラスのドキュメントを生成するために使用されます。
  */

また、ScalaではScalaDocというドキュメンテーションツールを使用して、ソースコード内のコメントからドキュメントを生成することができます。このようなコメントは、コードの可読性やメンテナンス性を向上させるために重要です。

ScalaDoc

[編集]

ScalaDocは、Scalaのソースコード内に記述されたコメントから自動的にドキュメントを生成するツールです。JavaのJavadocに似ていますが、Scalaの特性に合わせて拡張されたものです。

ScalaDocは、特定の形式のコメントに基づいて、ソースコードのAPIドキュメントを生成します。主な特徴は次のとおりです:

  1. コメントの形式: ScalaDocは、コメントが/** */で囲まれていることを前提としています。これにより、複数行のコメントや特定のタグを含むことができます。
  2. タグ: ScalaDocコメントには、さまざまなタグを使用して特定の情報を指定できます。例えば、@paramタグは関数のパラメータに関する情報を提供し、@returnタグは関数の戻り値に関する情報を提供します。
  3. HTML出力: ScalaDocは通常、HTML形式のドキュメントを生成します。これにより、ブラウザで簡単に参照できる形式でAPIドキュメントを提供できます。

ScalaDocを使用することで、ソースコードの理解や使用方法のドキュメント化が容易になります。特に大規模なプロジェクトやライブラリでは、正確で明確なドキュメントは非常に重要です。

以下は、ScalaDocの例です。これは、簡単なScalaのクラスとそのメソッドのドキュメントを示しています。

/**
  * This is a simple class representing a point in 2D space.
  *
  * @param x The x-coordinate of the point.
  * @param y The y-coordinate of the point.
  */
class Point(val x: Double, val y: Double) {
  
  /**
    * Calculates the distance between this point and another point.
    *
    * @param other The other point to calculate the distance to.
    * @return The distance between this point and the other point.
    */
  def distanceTo(other: Point): Double = {
    val dx = this.x - other.x
    val dy = this.y - other.y
    math.sqrt(dx * dx + dy * dy)
  }
}

/**
  * Main entry point of the program.
  */
object Main extends App {
  val p1 = new Point(0, 0)
  val p2 = new Point(3, 4)
  
  println(s"Distance between p1 and p2: ${p1.distanceTo(p2)}")
}

この例では、Pointクラスとそのメソッドに対するScalaDocコメントが提供されています。これにより、コードを理解する人や、このクラスやメソッドを使用する人が、それらの動作や使い方について理解しやすくなります。また、ScalaDocコメントには@param@returnといったタグも使用されており、それぞれパラメータや戻り値に関する情報を提供しています。

複数行コメントのネスト

[編集]

Scalaの複数行コメントはネストできます。つまり、複数行コメントの中にさらに別の複数行コメントを含めることができます。これにより、コード内で複数の段落や区切りのあるコメントを組み合わせることができます。

以下は、Scalaでの複数行コメントのネストの例です。

/*
  これは外側の複数行コメントです。
  
  /*
    これは内側の複数行コメントです。
    内側のコメントは外側のコメントの中にネストされています。
  */
  
  外側のコメントは内側のコメントの外側にあります。
*/

このように、Scalaでは複数行コメントを適切にネストすることができます。

; の自動挿入

[編集]

Scalaでは、JavaScriptやGoの様に ; が自動挿入されます。

var x = 1 + 2
+ 3

var x = 1 + 2 +
3

変数と型とリテラル

[編集]

Scalaは、関数型プログラミング言語ですが、純粋関数言語ではないので変数があります。

変数の宣言と初期化と参照

[編集]

Scalaで変数を使うためには、宣言が必要です。

val:イミュータブル変数宣言

[編集]
コード例
// val i: Int = 0 // !!! error: expected class or object definition !!!

object Main extends App {
    val i: Int = 123
    // i = 0 // !!! error: reassignment to val !!!
    println(i)
}
実行結果
123
1行目は、Object の定義の外で宣言しようとしても失敗する例です。
Scala には、グローバル変数はありません。
// から行末まではコメントになります。
object Main の中で、Int型の変数 i を宣言し、123 で初期化しています。
val で宣言した i に 0 を代入しようとしていますがエラーになります。
val で宣言した変数に、代入する事は出来ません。この様な変数をイミュータブルな変数と言います。
println(i) の様に、変数の値は変数名(この場合は i)を使って参照できます。

var:ミュータブル変数宣言

[編集]
コード例
// var i: Int = 0 // !!! error: expected class or object definition !!!

object Main extends App {
    var i: Int = 123
    i = 0
    println(i)
}
実行結果
0
1行目は、Object の定義の外で宣言しようとしても失敗する例です。
ミュータブルであっても、グローバル変数は作れません。
object Main の中で、Int型の変数 i を宣言し、123 で初期化しています。
var で宣言した i には 0 を代入する事ができます。この様な変数をミュータブルな変数と言います。

変数宣言に関する詳細

[編集]
未初期化はコンパイルエラー
[編集]
コード例(コンパイルエラー)
object Main extends App {
    var i: Int
    println(i)
}
コンパイル結果
1 error
Main.scala:2: error: only traits and abstract classes can have declared but undefined members
(Note that variables need to be initialized to be defined)
    var i: Int
        ^
_ を使ったディフォルト値を使った初期化
[編集]
コード例
object Main extends App {
    var i: Int = _
    println(i)
    var f: Boolean = _
    println(f)
    var s: String = _
    println(s)
    var m: Map[String, Int] = _
    println(m)
}
実行結果
0
false
null
null
識別子 _ を初期化に使うと、その型のディフォルト値で初期化されます。
数値は 0、Boolean は false、他は null がディフォルト値となります。
Go のゼロ値と似ていますが、明示的に _ で初期化するところが違います。
同じ式による複数の変数の初期化
[編集]
コード例
object Main extends App {
    val i, j, k = 42
    println(i, j, k)

    val u, v, w = new Array(5)
    println(u, v, w)

    var n: Int = 0
    val x, y, z = { n += 1 ; n }
    println(x, y, z)
}
実行結果
(42,42,42)
([Ljava.lang.Object;@7c16905e,[Ljava.lang.Object;@2a2d45ba,[Ljava.lang.Object;@2a5ca609) 
(1,2,3)
複数の変数を、一度に宣言し同じ[1]で初期化することが出来ます。
val i, j, k = 42k だけではなく ji も初期化されます。
初期化の式は、リテラルである必要はなく、初期化される宣言毎に評価されます。
val u, v, w = new Array(5)new Array(5) は、3回評価され、その都度新しいインスタンスが生成されます。
val x, y, z = { n += 1 ; n }{ n += 1 ; n } は、3回評価され、その都度インクリメントされるので、C言語の列挙型 enum と同じ機能が実現できます。どちらかと言うと Goiota ですね。
{ n += 1 } ではなく { n += 1 ; n } なのは、 n += 1 の値は n ではなく () だからです。
このため、Scala では x = y = 42 とは出来ず(x に () が入ってしまう)、(x, y) = (42, 42) のようにします。
パターンマッチ
[編集]

変数宣言にもパターンマッチングが使えます。

val (x, y) = ( 178, 901 )
val v @ (a, a) = ( 42, 78 )
val m @ Array(n0, n1, _*) = Array(2,3,5,7,11)
lazy val:遅延評価
[編集]
コード例
object Main extends App {
  var a = 3
  var b = 4
  lazy val c = { println("!"); a * b }
  a = 0
  println(c)
  a = 100
  println(c)
}
実行結果
!
0
0
lazy val で宣言された変数は、はじめて参照された6行目で評価
この時に参照されるのは、5行目で更新された a の値が参照されるので、クロージャではありません。
8行目で再び、c を参照すると、7行目での a の変更は反映されません。一度、評価されたら値は変わりません。

lazy var は出来ません。


val? var? どちらを使うべき?
変数を宣言するとき「valで宣言するか var で宣言すべきか?」、

言い換えると、「変数をイミュータブルにすべきか?」と言う質問になります。

関数プログラミングの観点からは、そもそも変数(=副作用)は邪道なのですが、 あえて変数を使うのであれば、まず val(イミュータブル)で宣言し、代入が行われるようなら var(ミュータブル)に変更するという基本戦略が考えられます。

幸いScalaは、イミュータブルな変数とミュータブルな変数に使える名前(変数名)の規則は同じで、参照するときも追加の記号は必要ありません。

型推論

[編集]

先の例では、変数を型を指定して宣言していました。 Scalaには、型推論機構があるので、初期値が与えられる変数宣言では初期値から変数の型が推論され型名を明示する必要はなくなります。

型推論
object Main extends App {
    val i = 123
    var j = 12
    println(i)
    println(j)
}
実行結果
123
12

数値のリテラル表現

[編集]

Javaの数値はオブジェクトではなくプリミティブ型ですが、Scalaでは数値もオブジェクトです。 また型名にも違いがあるので、対照表にしてみました。

JavaとScaleの型の対照表
Javaでの型 Scalaでの型 サイズ リテラル表現
char Char 2バイト 'A', '漢'
byte Byte 1バイト -128:Byte
short Short 2バイト 123:Short
int Int 4バイト 1234567890, -2147483648
long Long 8バイト 123L, -9223372036854775808L
float Float 4バイト 3.1415926536f
double Double 8バイト 3.1415926536
boolean Boolean 1バイト true, false

ブロック

[編集]

Scalaでは、カーリーブラケット構文を使ってブロックを表します。 ブロックの最後の式の値が、ブロックの値になります。

ブロックとブロックの値
object Main extends {
  val x = {
    val (a, b) = (1, 2)
    println("Hello!")
    a + b
  }
  println(x)
}
実行結果
Hello! 
3
val (a, b) = (1, 2)は、タプルを使った変数の宣言です。
これらの変数のスコープはブロック末の '}' までで、それ以降は参照できません(ブロックスコープ)。

関数

[編集]

Scalaでは、「0個以上のパラメータを受取ることの出来る式」を関数と呼びます。 他の言語では、アロー関数やラムダ式と呼ばれる概念です。

関数
object Main extends App {
    val add = (x: Int, y: Int) => x + y
    val msg = () => "hello!"

    println(add(1, 2))
    println(add(1999, 1))
    println(msg())
    println(((a: Int,b: Int)=>a*b)(9, 11))
}
実行結果
3
2000 
hello!
99
関数には名前がないので、変数に一旦に束縛し変数(変数名)を使って呼出します。
8行目では、変数を介さず定義したその場で実行しています(無名関数の即時実行)。

無名関数のアンダースコアを使った簡略表記

[編集]
コード例
object Main extends App {
  println((0 to 4).map(x => x * 3))
  println((0 to 4).map(_ * 3))
  println((0 to 4).reduce((x, y) => x + y))
  println((0 to 4).reduce(_ + _))
  println((0 to 4).map(x => x * x)) // 参照が二回以上ある場合は明示的に名前をつける
}
実行結果
Vector(0, 3, 6, 9, 12)
Vector(0, 3, 6, 9, 12)
10
10
Vector(0, 1, 4, 9, 16)
パイプラインスタイル
[編集]

パイプラインスタイルは、JavaScriptなどでよく使うメソッドチェインですが、イテレータメソッドが演算子の様に見える書き方です。

for と Range と ifの例をパイプラインスタイルで置換えてみます。

object Main extends App {
  // for (i <- 0 to 10 if i % 2 != 0) println(i)
  (0 to 10) filter { _ % 2 != 0 } foreach { println(_) }
}
実行結果
1
3
5
7
9

関数合成ではないので、(0 to 10) filter { _ % 2 != 0 } が先に評価され、そのオブジェクトに .foreach({ println(_) }) が提供されるので、場合によっては要素数の多いコレクション・オブジェクトが中間結果として生成されるので注意してください。

メソッド

[編集]

メソッドは、コードを再利用する仕組みで、関数と似ていますが、両者は別個のものです。 メソッドはclass、object、trait内でキーワードdefを使って定義します。

メソッド
object Main extends App {
    def add(x: Int, y: Int): Int = x + y
    def msg: String = "hello!"

    println(add(1, 2))
    println(add(1999, 1))
    println(msg)
}
実行結果
3
2000 
hello!
これだけ観ると、構文はともかく関数とメソッドの差は大きく感じられません。
強いていうと、空の引数リストは定義も呼出しも () を付けないところが違います。

これまで、頻繁に使ってきた println() も、Predefオブジェクトのメソッドで、Predefオブジェクトは暗黙に import されるので特別な準備なく使えました。

遅延評価パラメータ

[編集]

Scalaでは、メソッドや関数の引数は値渡しですが、遅延評価にすることが出来ます。 これによって、制御構造を関数定義する道が開きます。

メソッド
object Main extends App {
  def f1(x: Boolean, t: Unit, f: Unit) = if (x) t else f
  f1(true, println("True"), println("False"))
  
  def f2(x: Boolean, t: => Unit, f: => Unit) = if (x) t else f
  f2(true, println("真"), println("偽"))
}
実行結果
True
False 
真

メインメソッド

[編集]

メインメソッドは、プログラムのエントリーポイントです。

制御構造

[編集]

Scalaも、多くのプログラミング言語と同じく、「逐次」「分岐」「反復」の 3 種類の制御構造を持ちます。 Scalaの制御構造は、全て値を持ちます。この事は、Scalaの If は、C言語の三項演算子と等価であることを示します。

Scala 3 の新構文

[編集]

Scala 3 では、括弧() を使わない新しい構文が追加されました。 新構文の登場で、旧構文が使えなくなることはないので、Scala 2の為に書かれたプログラムが使えなくなるような事はありません[2]

旧・新構文の対応
Scala 2 Scala3
object Main extends App {
  val x = 0
  val xs = 0 to 4
  val ys = 0 to 5

  if (x < 0)
    "負"
  else if (x == 0)
    "零"
  else if (x > 0)
    "正"
  else
    "何"

  if (x < 0) -x else x

  while (x >= 0) print(x, " ")

  for (x <- xs if x > 0)
    yield x * x

  for (
    x <- xs;
    y <- ys
  )
    println(x + y)
}
object Main extends App {
  val x = 0
  val xs = 0 to 4
  val ys = 0 to 5

  if x < 0 then
    "負"
  else if x == 0 then
    "零"
  else if x > 0 then
    "正"
  else
    "何"

  if x < 0 then -x else x

  while x >= 0 do print(x, " ")

  for x <- xs if x > 0
    yield x * x

  for
    x <- xs;
    y <- ys
  do
    println(x + y)
}

分岐

[編集]

if

[編集]

if式は、条件式に基づいて分岐し分岐先の式を評価します。 if式の値は、分岐先の式の値です。

構文
if-expr := if '(' 条件式 ')' 式1 [ else 式2 ]
条件式は、Boolean 型でなければいけません。
else 式2の部分はオプショナルで、省略され条件式が false であった場合、if式はUnit 型の値 () 単位ユニットが式の値となります(要素個数ゼロのタプルです)。
elseif elsif のたぐいはないので、else if (条件式2) 式3 と続けます。
if式の例
object Main extends App {
    val i = 0
    
    if (i == 0)
        println("zero")
    else
        println("non zero")
        
    println({
        if (i == 0)
            "Zero"
        else
            "Non zero"
    })

    println({
        if (i != 0)
            "NON ZERO"
    })
}
実行結果
zero
Zero 
()

match

[編集]

C言語のswicth文は、式と一致するリテラルのラベルへの多方向GOTO文ですが、Scalaのmatch式はパターンマッチングに基づいています。

構文
match-expr :=   match {
( パターン =>  )+
}
式の値に、合致するパターンはないか上から順に走査し、合致したパターン対応する式が評価され、この値がmatch式の値となります。
式に対して、パターンがどれも合致しないとコンパイル時のフロー解析の結果わかった場合、コンパイルエラーになります。
このため、全てにマッチするパターン _ が最後に置かれることが常となります。
ただし式が、Boolean や enum の場合は、網羅チェックを台無しにするので _ を使うべきではありません。
match式の例
object Main extends App {
  val n = 10

  n match {
    case 1 => println("壱")
    case 2 => println("弐")
    case 3 => println("参")
    case _ => println("多数")
  }
  
  println({
    n match {
        case 1 => "一"
        case 2 => "二"
        case 3 => "三"
        case _ => "膨大"
    }
  })
  
  println({
    n match {
        case 1 => "一"
        case 2 => "二"
        case 3 => "三"
//        case _ => "膨大" // ここをコメントにしてしまうと、網羅性がほころびコンパイルエラーになる
    }
  })
}
実行結果
多数
膨大 
膨大

反復

[編集]

while

[編集]

while式は、条件式が true の間、式を評価しつづけます。 while式の値は、評価した式の値です。

構文
while-expr := while '(' 条件式 ')' 
条件式は、Boolean 型でなければいけません。
while式の例
object Main extends App {
    var i = 0
    
    while (i < 5) {
        println(i)
        i += 1
    }
    println(s"last = $i")
}
実行結果
1
2
3
4
last = 5
Scalaには、変数 ++ の様なインクリメント演算子はなく、変数 += 1 の構文を使います。
これは、Scala の整数はイミュータブルなためで、同じ理由で Ruby にもインクリメント演算子はありません。
また、変数 += 1 は、変数 = 変数 + 1の構文糖です。
さらに、変数 + 1 は、変数 .+(1)の構文糖です。
加算に限らず、Scalaの演算子は、メソッドの構文糖です。
i のスコープは、object Main なのでループを抜けても値を参照出来ます。
s"last = $i" は、加工文字列リテラル(processed string literal) で、文字列の中で変数の値を参照できます。

do - while

[編集]

Scala 3 で、do - while は廃止になりました。 while の条件式に繰返し部分と続行条件を書き、ループ本体は空にすることで do - while を模倣できます。

do - while の模倣
object Main extends App {
    var i = 10
    
    while ({
        println(i)
        i += 1
        i < 5
    }) {}
    println(s"last = $i")
}
実行結果
10 
last = 11

for

[編集]

Scalaのfor式は、ジェネレータに対してイテレーションを行います。

構文
for-expr := for '(' ジェネレータ ')' 
for と Range
[編集]
for式とRangeクラスの例
object Main extends App {
  println(0 to 4)
  for (i <- 0 to 4) println("to: " + i)
  println(0 until 4)
  for (i <- 0 until 4) println("until: " + i)
}
実行結果
Range 0 to 4
to: 0
to: 1
to: 2
to: 3
to: 4
Range 0 until 4
until: 0
until: 1
until: 2
until: 3
class scala.collection.immutable.Range$Inclusive
0 to 40 until 4 は、Rangeクラスのオブジェクトで範囲を表します。
0 to 40 until 4 は、Rangeクラスのリテラル、、ではなく tountil は演算子で
0.to(4)0.until(4) と言うメソッドの演算子表記です。
for と Range と if
[編集]

ジェネレータに、if で修飾することができます(Guardといいます)。Pythonジェネレータ式に似た表現です。

for式とRangeクラスとif式の例
object Main extends App {
  for (i <- 0 to 10 if i % 2 != 0)
    println(i)
}
実行結果
1
3
5
7
9
二重ループ
[編集]

2つのジェネレーターを ; で区切ると多重ループを構成出来ます。

二重ループ
object Main extends App {
  for (
    i <- 0 to 2;
    j <- 0 to 3
  ) {
    println((i, j))
  }
}
実行結果
(0,0)
(0,1)
(0,2)
(0,3)
(1,0)
(1,1)
(1,2)
(1,3)
(2,0)
(2,1)
(2,2) 
(2,3)
値を返す for
[編集]

for式と yeild の組合せでfor式は値を返すことが出来ます。

値を返す for
object Main extends App {
  println(for (i <- 0 to 4) yield i.toString)
}
実行結果
Vector(0, 1, 2, 3, 4)

脱出

[編集]

return

[編集]

ブロックからの脱出には、return を使います。 break や continue はありません。

例外処理

[編集]

ScalaにはJavaのthrows節がありません[3]。 すべての例外はメソッドの外側に投げることができます。

例外処理
object Main extends App {
  try {
    val n = 1
    n / 0
    println("return!")
  } catch {
    case e: Exception => {
      println("Exception caught.")
      println(e.getMessage)
      println(e.getStackTrace)
    }
  } finally {
    println("done!")
  }
}
実行結果
Exception caught.
/ by zero
[Ljava.lang.StackTraceElement;@35a74ae9 
done!
finally 節の式は、例外の有無に関係なく実行されます。
try 節や catch 節 で exit() すると、流石に finally 節は実行されません。

クラス

[編集]

Scalaは、関数型プログラミング言語であると同時に、オブジェクト指向プログラミング言語です。 より厳密に言うと、(プロトタイプベースではなく)クラスベースのオブジェクト指向プログラミング言語です。 クラス(class)は、オブジェクトを作る雛形で、クラスから new 演算子を使ってオブジェクトを作ることをインスタンス化、出来たオブジェクトの事をインスタンスと呼びます。

クラス定義とインスタンス化とメソッド

[編集]
コード例
object Main extends App {
  class Hello(val s: String = "world") {
    override def toString: String = s"Hello $s!"
    def print: Unit = println(s)
  }

  val hello1 = new Hello()
  println(hello1)
  hello1.print

  val hello2 = new Hello("my friend")
  println(hello2)

  print(s"""
Hello.getClass() === ${Hello.getClass()}
hello1 === ${hello1}
hello2.s = ${hello2.s}
""")
}
実行結果
Hello world!
world
Hello my friend!

Hello.getClass() === class Main$Hello$
hello1 === Hello world!
hello2.s = my friend
Ruby#クラスの例を、Scala に移植しました。
冒頭4行がクラス定義です。
クラス定義に、他のオブジェクト指向言語ならコンストラクタに渡すような引数が渡されています。
メンバーを公開するケースなら、この様に宣言的な引数リストを使うとメンバー定義と暗黙の初期値を与えられます。
toString は、オブジェクトを文字列化するメソッドで、Objectの同名のメソッドをオーバーライドしています。
print は、このクラスに独自なメソッドで、println() の値 == () == Unit を戻値型としています。

少しまとまったサイズのクラス

[編集]

Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、Scalaに移植。

ユーザー定義クラス
object Main extends App {
  class GeoCoord(val longitude: Double = 0.0, val latitude: Double = 0.0) {
    override def toString: String = {
      var (ew, ns) = ("東経", "北緯")
      var (long, lat) = (longitude, latitude)
      if (long < 0.0) {
        ew = "西経"
        long = -long
      }
      if (lat < 0.0) {
        ns = "南緯"
        lat = -lat
      }
      s"(${ew}: ${long}, ${ns}: ${lat})"
    }
    def distance(other: GeoCoord): Double = {
      val i = Math.PI / 180
      val r = 6371.008
      Math.acos(
        Math.sin(this.latitude * i) * Math.sin(other.latitude * i) +
          Math.cos(this.latitude * i) *
          Math.cos(other.latitude * i) *
          Math.cos(this.longitude * i - other.longitude * i)
      ) * r
    }
  }

  val Sites = Map(
    "東京駅" -> new GeoCoord(139.7673068, 35.6809591),
    "シドニー・オペラハウス" -> new GeoCoord(151.215278, -33.856778),
    "グリニッジ天文台" -> new GeoCoord(-0.0014, 51.4778)
  )

  Sites.foreach { case (k, v) =>
    println(s"${k}: ${v}")
  }

  val keys = Sites.map { case (k, v) => k }.toList
  val len = keys.size

  for (i <- 0 until len) {
    val (ksi, ksx) = (keys(i), keys((i + 1) % len))
    println(s"${ksi} - ${ksx}: ${Sites(ksi).distance(Sites(ksx))} [km]")
  }
}
実行結果
東京駅: (東経: 139.7673068, 北緯: 35.6809591)
シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778)
グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778)
東京駅 - シドニー・オペラハウス: 7823.269299386704 [km]
シドニー・オペラハウス - グリニッジ天文台: 16987.2708377249 [km]
グリニッジ天文台 - 東京駅: 9560.546566490015 [km]

ケースクラス

[編集]
コード例
object Main extends App {
  case class GeoCoord(longitude: Double = 0.0, latitude: Double = 0.0) {
    override def toString: String = {
      var (ew, ns) = ("東経", "北緯")
      var (long, lat) = (longitude, latitude)
      if (long < 0.0) {
        ew = "西経"
        long = -long
      }
      if (lat < 0.0) {
        ns = "南緯"
        lat = -lat
      }
      s"(${ew}: ${long}, ${ns}: ${lat})"
    }
    def distance(other: GeoCoord): Double = {
      val i = Math.PI / 180
      val r = 6371.008
      Math.acos(
        Math.sin(this.latitude * i) * Math.sin(other.latitude * i) +
          Math.cos(this.latitude * i) *
          Math.cos(other.latitude * i) *
          Math.cos(this.longitude * i - other.longitude * i)
      ) * r
    }
  }

  val Sites = Map(
    "東京駅" -> GeoCoord(139.7673068, 35.6809591),
    "シドニー・オペラハウス" -> GeoCoord(151.215278, -33.856778),
    "グリニッジ天文台" -> GeoCoord(-0.0014, 51.4778)
  )

  Sites.foreach { case (k, v) =>
    println(s"${k}: ${v}")
  }

  val keys = Sites.map { case (k, v) => k }.toList
  val len = keys.size

  for (i <- 0 until len) {
    val (ksi, ksx) = (keys(i), keys((i + 1) % len))
    println(s"${ksi} - ${ksx}: ${Sites(ksi).distance(Sites(ksx))} [km]")
  }
}
実行結果
東京駅: (東経: 139.7673068, 北緯: 35.6809591)
シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778)
グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778)
東京駅 - シドニー・オペラハウス: 7823.269299386704 [km]
シドニー・オペラハウス - グリニッジ天文台: 16987.2708377249 [km]
グリニッジ天文台 - 東京駅: 9560.546566490015 [km]
コンストラクタ引数に val がなくても、自動的にフィールドが宣言されます。
var なフィールドを希望する場合は、var を明示します。
インスタンス化する時、new は必要ありません。
コンパニオンオブジェクトが自動的に生えてきます。

シングルトン・オブジェクト

[編集]

object は、たった1つのインスタンスを持つクラス(=シングルトン)です。 これは、lazy valのように、参照されたときに(遅延して)生成されます。

object は、トップレベルの値としてはシングルトンであり、 クラスのメンバやローカルな値としては、lazy val と全く同じ振る舞いをします。

シングルトン・オブジェクトの定義

[編集]

シングルトン・オブジェクトは値です。シングルトン・オブジェクトの定義はクラスのように見えますが、object というキーワードを使用します。

シンプルなシングルトン

object Simple

トレイト

[編集]

トレイト(trait)は、Javaインターフェース抽象クラスの中間的な存在です。 実装を持ったインターフェースで、コンストラクタのパラメータを持つことができませんが、ミックスイン( mix-in )することが出来ます。

  • 抽象クラスと同様に、トレイトは具象メンバを持つことができますが、インスタンス化することはできません。
  • インターフェイスと同様に、クラスは複数のトレイトのメンバを継承することができます。
  • クラスはトレイトで拡張することができます。
    class C extends Trait1 { ... }
    
  • クラスや他の特徴を継承することができます。
    trait T1 extends Class1 { ... }
    trait T2 extends Trait1 { ... }
    
  • クラス、オブジェクト、トレイトはトレイトの中で混在することができます。
    class C extends Trait1 with Trait2 with Trait3 { ... }
    val obj = new Class1() with Trait1 with Trait2 with Trait3 { ... }
    

脚註

[編集]
  1. ^ 値ではなく式です。
  2. ^ 2から3へのアップデートと言うと、python2とpython3の非互換性の悲劇を思い起こす方も多いと思いますが、Scaleでは原則的に下位互換性があります。
  3. ^ Javaの場合は、例外を発生させる可能性のあるメソッドは、throws節で発生させる例外の種類をすべて宣言する必要があります。 ただし、RuntimeException(RuntimeExceptionの派生クラス)の場合は、throws節で明示的に記述する必要はありません。

参考文献

[編集]
  • Scala Language Specification | Scala 2.13”. École Polytechnique Fédérale, Lausanne (EPFL) Lausanne, Switzerland (2022年6月10日). 2022年6月24日閲覧。
  • Scala 3 Reference”. École Polytechnique Fédérale, Lausanne (EPFL) Lausanne, Switzerland (2022年6月1日). 2022年6月24日閲覧。

外部リンク

[編集]
このページ「Scala」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。