3. Next Steps in Scala
この章と前章でscalaで有用なスクリプトを書けるレベルの知識が身に付く。
実際に使わないと分からない。
Step 7. Parameterize arrays with types
インスタンス化する時はnewを使う。
型パラメータは[]で値パラメータは()でコンストラクタに渡す。
val greetStrings: Array[String] = new Array[String](3)
greetStringsは普遍だがgreetStrings(0)はArrayがmutableなので変更出来る。
一引数でレシーバが明示されている場合は.や()を省略できる。
(0).to(2) は 0 to 2 と書ける。
"Console println 10"とかける。
"println 10"はレシーバが明記されていないのでNG。
すべてがメソッドでオペレータではないので、オペレータの再定義は存在しない。
メソッドが再定義される。
1 + 1 は (1).+(1)のこと
どんなオブジェクトでも()を付けたたらapply()メソッドに変換される。
Array型のインスタンスであってもそれは同じ。
greetStrings(0)はgreetStrings(0).apply(0)に変換される。
scalaではArrayも普通のクラスで特別ではない。なので()でアクセスする。
代入もコンパイル時にupdate()メソッドに変換される。
scalaでは全てがメソッドを持ったオブジェクト。例外を覚える必要がない。
更にコンパイル時にJavaのbuiltinに変換されるので性能も悪くならない。
配列は下記で初期化できる。
val numNames = Array("zero", "one", "two")
実際は、
val numNames = Array.apply("zero", "one", "two")
これはArrayのcompanion objectとして定義されている。
companion objectはJavaのstaitc methodと似ている。
** 概念は統一的だが簡素な記法をsyntactic sugarとして用意している。
Step 8. Use lists
Array[A]は要素が型Aのmutableな配列
List[A]は要素が型Aのimmutableな配列
Listを変更しているように見えても、実際は新しいListが生み出されている。
関数型的にするにはimmutableにする必要がある。
参照透過になってシンプルで再利用性が高まる。
型システムによるエラーチェックが効果的に働くようになる。
:::が結合で::がcons
:で終わるメソッドは右記述子。他は通常の左記述子
?? Listには長くなると実行時間が掛かるからappendがない?? :::は良いの??
mutableリストでappendしてからtoListでimmutable化するとあるが。。。
空リストはNil
?? Nothingは型無しの意味??
Step 9. Use tuples
tupleはimmutable
別々の型を格納できる
val t: (Int, String) = (1, "hoge")
アクセスは._N
t._1
1始まり。
static tuplesは歴史的に1始まり。HaskellとかMLも。
t._Nの戻り値の型がNに依存して変わるのでNをパラメタとする単一のメソッドはNG。
?? これもオブジェクト? _1はメソッド?? なんか違う気もする??
?? tupleは最大22要素まで??
Step 10. Use sets and maps
scalaでは命令型と関数型両方をサポートするためにcollectionにmutable, immutable両方を用意している。
SetとMapはクラス階層の中で可変性を区別している。
Setはdefaultではimmutalbe
var jetSet = Set("a", "b")
jetSet += "c"
これは新しいSetを生み出してそれをjetSetに代入している。
jetSetに保存されている値を書き換えているのでvarの必要がある。
jetSet = JetSet + "c"と同じ意味。
?? コンパイラが変換している??
mutableのSetを使いたいときはimport等でmutableの型を指定する。
import scala.collections.mutalbe.set
val jetSet = Set("a", "b")
jetSet += "c"
これはSetの中身を書き換えている。新しく生み出していない。
jetSetが指しているものは同じなのでvalで大丈夫。
+=はメソッドでJetSet.+=("c")と同じ。
** Setは重複を許さないcollection
** Mapは連想配列、辞書
Setと同様にclass階層でmutablityを区別している。
defaultではimmutable
mutableを使うときはimport等でクラス階層を指定
import scala.collection.mutable.Map
val m = Map[Int, String]()
m += (1 -> "hoge")
1 -> "hoge"は(1).->("hoge")でtuple (1, "hoge")を返す。
これをm.+=の引数として渡すと追加される。
key, valueが別々の型でもOK
val m = Map(1 -> "a", "hoge" -> 2)
?? 型にはAnyやNothingもある??
Step 11. Learn to recognize the functional style
scalaでは関数型の書き方を推奨している。
関数型かどうか判断の一つはvarがない事。
varをなるべく使わないようにすれば関数型の書き方に近づける。
varを減らすことで、簡素で分かりやすくバグが少なく出来る。
関数型かどうか判断の一つは副作用がない事。
副作用は最小限にすべき。
副作用がある部分を分離すべき。
副作用がなくなればテストしやすくなる。
Assertを使った単体テストで十分になる。
varも副作用も悪ではない。それを使うことが適切な場合もある。
バランス感覚としては、
基本的にはval, immutable, no side effectで書く。
相応の理由がある時のみvar, immutable, side effectを使う。
** var, immutable, side effectを分散させずに少ない箇所に閉じ込めるのも大切。
Step 12. Read lines from a file
** Source.fromFile(filename).getLinesは2.8から改行はstripする様に変更されている。
ファイルの内容の読み方。
** scalaプログラムの読み方も学べる。
scala.io package, Source class, Iterator[String], standard error stream
Iteratorは一度実行すると消費されてしまうが、Listに変換すれば何回も使える。
Listにファイルから読み込むコストは1回だけ。
** stream型のプログラムより一括して読む込む分メモリを使いそう??
畳み込みreduceLeftを使ってループを変換してvarを消している。
** mapも使えると思うが、そこまでは行っていない。
** valでも同じ名前に別の値を再定義できる。定義内のスコープでは変更出来ない。
for (line <- Source.fromFile(args(0)).getLines) {
val numSpace = maxWidth- widthOfLength(line)
println(" " * numSpace + line.length + " | " + line)
}
?? ダイナミックスコープ? レキシカルスコープ?
?? valに関数リテラルを定義するのと、defの差はある??
Conclusion
これで小さいタスクを実行するためのスクリプトを書けるようになったはず。
** そんなに簡単では無い気がするが、リファレンス読みながら書ける?
** ちょっとしたscriptの実行にJVMの立ち上げなどもあるので実行時間が掛かる。
** 関数型スタイルでは結果から逆向きに必要な部品を作成する。
** TDD(test driven development)と相性が良さそう。
** 実際にやってみると関数型スタイルはコードの部分移動(refactor)が楽。
?? scalaの型推論はhaskellの推論より浅い?? compile時により束縛??
0 件のコメント:
コメントを投稿