2014年4月12日土曜日

Programming in Scala, First Edition: Chapter 6

6. Functional Objects
  関数オブジェクト(可変状態を持たないオブジェクト)定義するクラスを作る。
  有理数を不変オブジェクトとして定義する。
  class parameters onstructors, methods and operators, private members,
  overriding, checking preconditions, overloading, and self references

6.1 A specification for class Rational
  有理数(分数)クラスの仕様。
  四則演算を出来る様にする。約分もする。
  数学的見地からも有理数はimmutable。演算結果は別の有理数になる。
  scalaでnative language supportに見えるlibraryを作れる事を体験する。

6.2 Constructing a Rational
  コンストラクタを設計するのにクラスの使用者がどうやって新しいオブジェクトを生成するのかを考えると良い。
  Rationalは不変なので生成時に必要な全ての初期値を与える必要がある。
    class Rational(n: Int, d: Int)
    実装がないときは{}は省略できる。
    引数はclass parametersと呼ばれる。
    compilerがclass parametersを引数にとるprimary constructorを生成する。
    class parametersはimmutableでclass内のどこからでも参照出来る。
      javaと違いinstance変数としての宣言して代入する等は不要。
  scalaではjavaと違ってconstructorの実装はclassの地の部分に記載する。
    これでテンプレート的な部分は書かずに簡素化できる。
    地の文はnewされるタイミングで実行される。コンストラクタの実装なので。
  immutableにすることのトレードオフ
    immutalbeはmutableより良い所が多い。
      状態を持たないので分かりやすい。
      不変なのでバックアップする必要ない。
      複数threadからアクセスしても安全。
      Hashのkeyとしても安全に使える。

6.3 Reimplementing the toString method
  defaultではclassはjava.lang.ObjectのtoStringを継承している。
    でもこのままではあまり役に立たない。
    debugに役立つ情報を表示するべき。今回ではobjectの値。
  override修飾子をdefの前につけるとoverride出来る。

6.4 Checking preconditions
  オブジェクト指向の利点にデータをカプセル化する事で有効性を確実にする事がある。
  Rationalでも分母が0の場合は生成出来なくしたい。
  一番良いのはpreconditionを定義する事。
    一つの方法require()を使う。
    require()は引数がfalseだとIllegalArgumentExceptionを投げる。

6.5 Adding fields
  足し算addメソッドを定義する。
  class parametersはclassの外からは参照出来ない。
  fieldsを定義する。
    immutableなのでvalで定義する。
    requireはvalの定義の前にする。
  ?? class parametersを使っていた場所もfieldsに変更したが不要??
  ** defaultではpublicのfieldsになる。

6.6 Self references
  thisでメソッドのレシーバインスタンスを示せる。
  class定義内ではスコープ内なので省略可能。
    省略すると何も残らないときのみ省略不可能。
      def min(that: Rational) = if (this < that) this else that

6.7 Auxiliary constructors
  def this(...)でシグネチャ違いの補助コンストラクタを定義出来る。polymorphism
    Auxiliary constructorsの最初に必ずprimary constructorを呼ぶ。
      this(...)で呼び出す。
        primary constructorのシグネチャで。
    primary constructorのみがsuper classのcostructorを呼べるから。
      ここはjavaより制約が厳しいが、設計のトレードオフになる。
      この制約のお蔭で、scalaのconstructorは簡素に書ける。

6.8 Private fields and methods
  private修飾子でfieldsもmethodsもprivateに出来る。
  constructorのfieldsの初期化は「ソースコード上の順番」で実行される。
    ?? 関数型ならfieldsが使用される時に初期化処理を実行しても良いのでは?
      ** lazy valを使えば出来る。
  再起呼び出しを使うmethodは戻り値の型定義を省略出来ない
  ?? 未初期化の変数にもエラーにならずにアクセス出来てしまう??
    ** -Xcheckinit で実行時に例外を投げられる。
    ** forward reference問題として有名。chapter 20.5で扱われている??
    ?? constructorから未初期化のvalのインスタンス変数にアクセス出来てしまった??
    ?? アクセスする時に必要な初期化処理を実施してくれる訳ではない??
    ?? これって関数型的(遅延評価)ではないのでは??
      ** lazy valを使えば出来る。
  ?? 関数定義はソースコードの初期化処理の下に書いても実行出来てはいる。
  ?? 再起(or相互)呼び出しの無限ループ対策はどうなっている??
  ?? scalaは順番非依存にはうるさくない??
  ** 最大公約数を求めるアルゴリズムが単純だが難しい。。。
  ** scalac -help, scalac -Xでオプションが出る。沢山ある。。。
    ** scalaのインタープリタでも使える。

6.9 Defining operators
  単にメッソド名を+等にするだけ。
  ?? def + (...) と+の後にスペースを入れるのは推奨スタイル??

6.10 Identifiers in Scala
  alphanumeric
    普通の識別子
    英文字もしくは_から始まって、英数字もしくは_が続く。
    $も文字に含まれるが、compilerが自動生成する識別子に使われる。
      ユーザは使えない。名前の衝突が起きる。
      javaからscalaを使うときにはjava側で使う場合がある。
    大文字小文字は慣習的にjavaのcamel-caseに従う。
      fields, methods, local variables, functionsは小文字始まり。ex. flatMap
      classes, traitsは大文字始まり。ex. HashSet
    _も使えるが慣習的にあまり使わない。
      javaに合わせている。
      _ が識別子以外の特別な意味を持つこともあるから。
        "val name_:Int = 1"はエラーになる。_の後ろにスペース必要。
    constantsの命名はjavaの慣習と異なる。
      constantsはvalの事ではない。
        valは変更不能な変数で、色々な値に初期化される。
      constantsは scala.Math.Pi の様に値が固定的に決まるもの。
      javaでは全部大文字の_区切りだが、scalaでは大文字始まりのcamel-case
  operator
    operator charactersからなる。 +, :, ?, ~, #
    組み合わせて長くするのはOK。++, :::, <<?>>
    内部では$と文字のjavaの表記に変換されている。
      :-> は $colon$minus$greater
      javaから使うときは$に変換後の表記を使う。
    x<-yはjavaでは x < - y と等価だが、scalaでは x <- y
      javaと同じように解釈させたかったらスペースが必要。
      operatorが任意文字列長だから。
  mixed
    alphanumericの後に_とoperator
    unary_+は単項演算の+
    myvar_=は代入。
      ?? さらにpropertiesをサポートするために自動生成される。?? (chapter 18)
  literal
    back ticksで括られた任意の文字列。ex. `yield`
      ``の中は予約語などどんな文字でもOK。
    yieldはscalaでは予約語なので、Thread.yield()でjavaのmethodを呼び出せない。
      Thread.`yield`() なら呼び出せる。

6.11 Method overloading
  同名で型シグネチャの違うメソッドを定義できる。overloading、polymorphism
  best matchしたメソッドが実行される。
    javaと同様。
    一つに絞れない場合は、compile時に"ambiguous reference"エラー。
  ?? 暗黙変換や型推論があるから完全にmatchしている必要なないと言う事??

6.12 Implicit conversions
  ** 誤用が多いので明示的に指定しないと使えないようになっていた。
     compiler option -language:implicitConversions.
     import scala.language.implicitConversions
  ** implicit parametersを使う方が適切な場合が多いらしい。
  ?? Rationalが標準クラスではないのでIntをレシーバにすると+メソッドが使えない??
    ?? 標準クラスかどうかと関係ある??
    ?? Intを拡張してメソッド追加できない??
  インタープリタで下記を実行する。
    scala> implicit def intToRational(x: Int) = new Rational(x)
  2 + new Rational(3) が実施可能になる。
  Intの2が暗黙変換でRationalに変換されるため。
  ただし、暗黙変換が必要なスコープで変換関数を定義する必要がある。
    Rationalクラス側で定義してもダメで、使う側で定義する必要がある。
    chapter 21で必要な場所に定義をどうやって持っていくかも説明がある。
  どういう時に変換が働くかは後で説明。chapter 21
    とても強力なので誤用も多い。

6.13 A word of caution
  力には責任が伴う。
  operator methodsもimplicit conversionsもコードを分かり難くする危険性がある。
    implicit conversionsはcompilerが暗黙的に行う
      ユーザには何時何が実行されているのか分かり難いかもしれない。
    operator methodsは普通はコードを簡素で読みやすくする
      そのためにはoperatorの意味を理解して覚えている必要がある。
  読みやすく理解しやすいようにライブラリを設計すべき。
    単に簡素化出来るだけではダメ。
    簡素化は読みやすさに繋がるが、やり過ぎてしまう事もある。
   簡素化と読みやすさの両立が出来て生産性の向上が出来る。
  ** 急に思想的な話になった。ここがscalaの欠点と言われているのかもしれない。
  ** 自由度が高すぎると言う事。

6.14 Conclusion
  Rationalクラスについてはこの本の後半でも更に拡張する
  chapter 28では==のoverrideやhashを使った改善を行う。
  chapter 21ではimplicit methodを使用するスコープに持っていく方法を扱う。

0 件のコメント:

コメントを投稿