1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Scalaスケーラブルプログラミング第4章

Last updated at Posted at 2018-10-04
1 / 18

Scalaスケーラブルプログラミング第4章


4.1. クラス、フィールド、メソッド


はじめに

チェックサム (checksum)とは
「送ったデータ大丈夫?」ってチェックするための方法のひとつ。
もしくは
そのチェック用にくっつけてやる「送るデータを全部足した値」のこと

  • シングルトンオブジェクト何に使うの?

    • メリットはよくわからんがそう言うものだと思えばいいのかな。。。
  • 4章では2つのファイルを書きます

    • ChecksumAccumulator.scala
    • Summer.scala
  • この2つがあると、p87の下記が実行できるようになる

$ scala Summer of love
  • Scalaのクラスとオブジェクトの基礎、
  • アプリケーションのコンパイル、実行方法について学ぶ。

  • クラスとは
    • オブジェクトの青写真(「図面」とか訳した方がいいのでは?)
    • クラスからオブジエクトを作成できる
  • クラス定義の書き方は↓
ChecksumAccumulator.scala
class ChecksumAccumulator{
  //ここにクラス定義
}
  • これがあると、下記のようにオブジェクトを作成できる
    • newをつけてclassを呼べば良い
new ChecksumAccumulator

クラス定義に書くこと

  • クラス定義の中には何を書けば良いか?

    • フィールドとメソッドの2つ。まとめてメンバー
  • フィールドとは?

    • オブジェクトを参照する変数。val,varで定義
    • オブジェクトのデータや状態を保持
  • メソッドとは?

    • 実行可能なコードを格納。defで定義
    • データを使って、オブジェクトが行うべき仕事を実行
  • クラスのインスタンス生成時にメモリーが確保される(これ意識するのか?)

  • フィールドを追加して見ると↓

ChecksumAccumulator.scala
class ChecksumAccumulator{
  var sum = 0 //フィールド
}
  • インスタンスを2個作ると↓
val acc = ChecksumAccumulator
val csa = ChecksumAccumulator
  • この2つのインスタンスを見ていこう

メモリ内のオブジェクトイメージはどうなるか?

  • acccsaの2つのオブジェクトができる。
    • それぞれ、sumというフィールドを持っている。
    • それぞれのsumというフィールドは独立していて、一方に値を再代入してももう一方は変わらない
    • インスタンス専用の変数のため「インスタンス変数」とも呼ばれる。
  • accvalなので書き換えられない。一方、sumvarなので書き換えられる。
    • accが初期化時に代入されたのと同じChecksumAccumulatorであることは確定。
    • 一方、フィールド(sum)は変化しているかも。
  • オブジェクトを堅牢に保つには?
    • フィールド(sum)を非公開にする。
    • するとクラス外からの直接的なアクセスできない。
    • 非公開フィールドは同じクラスで定義されたメソッドだけしかアクセスできないので、情報を更新するコードはクラス内のみ。
  • 非公開にするには、フィールドの前にprivateというアクセス修飾子を挿入すれば良い
    • ちなみに、scalaはデフォルト公開
ChecksumAccumulator.scala
class ChecksumAccumulator{
  private var sum = 0 //privateになった!
}
  • クラスの外からsumにアクセスできない↓
val acc = ChecksumAccumulator
acc.sum = 5 //非公開のためアクセスできない

Class内でsumをいじる

  • sumにアクセスするには、クラスの本体で定義されたコードのみ。
    • addchecksumを追加してみる
ChecksumAccumulator.scala
class ChecksumAccumulator{
  private var sum = 0
  def add(b: Byte): Unit = {
    sum += b
  }
  def checksum(): Int = {
    return ~(sum & 0xFF) + 1
  }
}
  • メソッドパラメータ(上記のb)はvalのため、メソッド内で書き換えられない
  • 上記を単純化する4つの手法
  1. return文は除く
    2. returnなければメソッド内で計算された最後の値を返す
    3. 複数のreturnは書かないこと推奨
  2. メソッドが単一の結果式を計算するだけなら、中かっこ省略可
  3. 結果式が短ければ、defと同じ行に書ける
  4. 結果型を省略し、型推論を利用可能(下記ではしてない)
    5. しない理由:publicメソッドの結果型を明示するのは多くの場合適切
ChecksumAccumulator.scala
class ChecksumAccumulator{
  private var sum = 0
  def add(b: Byte): Unit = { sum += b } //中括弧を省略しないのは視認性のため?
  def checksum(): Int = ~(sum & 0xFF) + 1
}
  • 結果型がUnitのメソッドは、副作用を生じさせるために実行。
  • 副作用だけを目的として実行されるメソッドは、手続き

4.2. セミコロン推論


  • 本来、文の末尾にはセミコロンを入れるべき。
  • Scalaはセミコロンを類推してくれるので、原則、文の末尾のセミコロンは入れても入れなくて良い
  • 1行に複数の文を書くときは必要
val s = "hello"; println(s)
  • 複数行にまたがる時、正しい位置で分割してくれる(類推をうまくやってくれている)
    • 下記は4行だが、1つの文章として扱われる
if (x < 2)
  println("too small")
else
  println("ok")
// 下記のルールのどれに当てはまる??
  • 類推のロジックは、下記の条件に当てはまらなければ、行末はセミコロンとして扱われる。
      1. 当該行の末尾が、文法的に認められていない単語になっている(ピリオド、中置演算子)
      1. 次の行が文の先頭として認められてない単語になっている
      1. かっこや、角かっこの中にいる状態で文末になっている
x
+ y
// 1,2,3どれにも当てはまらないので、行末はセミコロンとして扱われる→1文でない
(x
+ y)
// 3 に当てはまるため、行末はセミコロンでない→1文として扱われる
x +
y +
z
// 1 に当てはまるため、行末はセミコロンでない→1文として扱われる。

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


  • ScalaはJavaよりオブジェクト指向
    • 理由:クラスが静的メンバーを持てないから
  • Scalaにはシングルトンオブジェクトがある
    • シングルトンオブジェクトは、クラス定義とよく似ている
    • classの代わりにobjectを用いる
import scala.collection.mutable // 変更できる方
object ChecksumAccumulator { //シングルトンobject!下記のクラスと同じ名前。「コンパニオンオブジェクト」と呼ぶ。同じソースファイルで定義必要。
  private val cache = mutable.Map.empty[String, Int]
  def calculate(s: String): Int =
    if (cache.contains(s))
      cache(s)
    else {
      val acc = new ChecksumAccumulator //下のコンパニオンClassのインスタンスを作成
      for (c <- s) // 文字列を1字ずつ処理
        acc.add(c.toByte) // 1字をbyteに変換し、accのメソッドaddで非公開フィールドsumに足し上げ
      val cs = acc.checksum() // accのchecksumを実行
      cache += (s -> cs) // キー(s)と整数値(cs)をcacheマップに追加
      cs // calculateの結果として返す
    }
}

//下記が省略されてるか
class ChecksumAccumulator{ //シングルトンobjectのコンパニオンclass
  private var sum = 0
  def add(b: Byte): Unit = { sum += b }
  def checksum(): Int = ~(sum & 0xFF) + 1
}
  • シングルトンオブジェクト、コンパニオンクラスは互いの非公開メンバーにアクセスできる(privateのついているもの)
    • 上記では特にアクセスしてない?

  • 実行は下記で行う
ChecusumAccumulator.calculate("Every value is an object.")
  • シングルトンオブジェクトは一人前のオブジェクト
    • シングルトンオブジェクトの名前(ChecksumAccumulator)はネームタグ(p79のaccと比較)
  • シングルトンオブジェクトの定義だけがあっても、ChecksumAccumulator型の変数は作ることはできない
    • コンパニオンクラスがないといけない
  • シングルトンオブジェクトは、スーパークラスを継承し、トレイトをミックスインできる。(スーパークラスとはコンパニオンクラスとは別?)
    • これらの型のメソッド、変数、パラメーターを活用できる
  • クラスはパラメータを取れるが、シングルトンオブジェクトはパラメータを取れない
    • シングルトンオブジェクトはnew キーワードでインスタンス生成できないのでパラメーターを取れない(p60がパラメータの例か)
    • 個々のシングルトンオブジェクトは自動生成クラスのインスタンスとして実装。
    • 初期化瀬マンティクスを持っている。
    • シングルトンオブジェクトは何らかのコードから初めてアクセスされたときに初期化される。(newしなくてもインスタンスができる?)
  • コンパニオンクラスと同じ名前を共有しないシングルトンオブジェクトは、「スタンドアロンオブジェクト」

4.4 Scalaアプリケーション


  • Scalaプログラムを実行するには、
    • mainメソッドを持つ、
    • スタンドアロンシングルトンオブジェクト(コンパニオンクラスがないシングルトンオブジェクト)
  • の名前を指定しなければならない
  • mainメソッドは
    • パラメータとしてArray[String]
    • 結果型はUnit
  • 適切なシグネチャーのmainメソッドを持つスタンドアロンオブジェクトはどれでもアプリケーションのエントリーポイントして使うことができる(謎)
Summer.scala
import ChecksumAccumulator.calculate // import している

object Summer {
  def main(args: Array[String]) = { // mainメソッド、Array[String]、Unit型
    for (arg <- args) // 引数を1つずつ
      println(arg + ": " + calculate(arg)) // 単語を表示し、そのchecksumを返す
  }
}
  • 上記は
    • mainメソッドを持つ,スタンドアロンシングルトンオブジェクトがある
    • mainメソッドはパラメータがArray[String],結果型はUnit(省略されているが)
  • import文はChecksumAccumulatorオブジェクトのcalculateメソッドをインポートしている
    • インポートすると、メソッドの単純名を使えるようになる
    • ちなみに、ScalaはPredefと言うシングルトンオブジェクトのメンバーを暗黙農地に全てのScalaソースファイルにインポートしている。printlnassertはこのオブジェクトのメソッド。

# 完成したので実行しよう

  • ここまでで、
    • ChecksumAccumulator.scala
    • Summer.scala
  • を作成した。
  • ファイル名はどうつけるべきか?
    • .scalaファイルには好きな名前をつけることが可能
    • スクリプトでないファイルの場合は、格納しているクラスと同じ名前をつけるスタイルが推奨
  • Check~.scalaとSummer.scalaは、定義で終わっているのでスクリプトではない
    • スクリプトは最後が結果式
    • スクリプトでないファイルは、Scalaコンパイラーでコンパイルし、生成されたクラスファイルを実行する必要
  • コンパイルを行うには基本コンパイラーscalac
    • 待ち時間がある
    • コンパイラーを起動するたびに、jarファイルの内容をスキャンするなどの初期作業に時間を費やしてから、ユーザーが指定したソースファイルを読むから
$ scalac ChecksumAccumulator.scala Summer.scala
  • fscというコンパイラーデーモンもある
    • 良くわからんが、1回実行すれば、2回目以降の実行時間が削減される
$ fsc ChecksumAccumulator.scala Summer.scala
  • scalacかfscを実行すると、Javaクラスファイルが生成され、それをscalaコマンドで実行できる
    • mainメソッドを格納するスタンドアロンオブジェクトの名前を指定する
$ scala Summer of love // Summerはスタンドアロンオブジェクトの名前

4.5 Appトレイト


  • scala.Appと言うトレイトを提供して、コード入力そ少しでも減らせるようにしている(謎)
import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends App {
  for (season <- List("fall","winter","spring"))
    println(season + ": " + calculate(season))
}
  • 書き方
    • シングルトンオブジェクトの名前の後に extends Appを書く
    • mainメソッドを書かずに、それに入れるはずだったコードを中括弧の間にかく
    • argsと書けば、コマンドライン引数にアクセスできる
  • 他のアプリと同じようにコンパイル、実行できる(def mainを書かずに済むので便利、と言うことか?)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?