『やさしいScala入門 平明な例と演習問題で学ぶ』の感想というかダメだしの続き。言いたいことは前のエントリで一通り言ったので、後は重箱の隅をつつくような話。
本文について
「イミュータブルなプログラミング」?
p.3
Scalaでは、純粋関数型言語的なイミュータブル(immutable、不変)なプログラミングも、手続き的なミュータブル(mutable、不変)なプログラミングもできます(例を後で示します)。
例を後で示しますといいつつ、全然例が無かったぞとか言いたいがそれはともかくとして、イミュータブルなプログラミング(やミュータブルなプログラミング)って一体何?おそらくは、基本的にイミュータブルなデータ構造を使うプログラミングスタイルの事を指しているのだろう。だが、イミュータブルとかミュータブルはデータ構造の文脈において使うものであって(e.g. イミュータブルなデータ構造)、イミュータブルなプログラミングとか意味不明である。
「Scalaでは関数定義などの定義も値に含める」?
p.4
Scalaインタープリタのコマンドプロンプトに1行のコードを入力するたびに結果が表示されます。それまでに入力した値(Scalaでは関数定義などの定義も値に含める)は保存されるので、入力しながらプログラムを作成することもできます。
新事実発覚!Scalaでは関数定義も値だった。ということは、
scala> val x = (def foo(x: Int) = x)
のようなこともOKなわけだ*1。
Scalaではコンパイルしてもscalaインタープリタから実行される?
p.12
最初に、テキスト形式でScalaのプログラムファイルを作成します。そして、そのファイルをScalaコンパイラでコンパイルします。
p.12 Note
(上の記述に続いて)
実際には、Javaプログラムを実行するためにコマンドであるjavaから直接実行するのではなく、scalaインタープリタから実行します。
この場合、scalaコマンドは単にscala-library.jarをクラスパスとして指定する等の面倒な作業を代わりに行うだけであり、別にscalaインタープリタが実行しているわけではない。また、scalaコマンドを使わずとも、scala-library.jarをクラスパスに含めれば普通にjavaコマンドで実行できる。
ちなみに、scalaコマンドは主に以下の三つの役割を持っており、コンパイル済みのscalaプログラムを実行する際には、3.の動作を行う。
- REPLのランチャー。
- scalaのスクリプト(トップレベルに直接実行文が書かれているもの)をラップし、コンパイル・実行を行う。
- scalaライブラリ(libディレクトリにある)をクラスパスに追加するなどしてから、コンパイル済みscalaプログラムを実行する。
scalaコマンドにファイル名を与えた場合、インタープリタで実行される?
Scalaのスクリプトとして実行したいコードを次のようにファイルに記述し、それをScalaの起動時に引数として与えることで、インタープリタでロードして実行することもできます。
var msg="Hello, Scala" println(msg)
scala スクリプトファイル
としたとき、別にインタープリタでロードされて実行が行われるのではなく、単にscalacでコンパイル可能な形にラップして(具体的にはobject型でくるむ)コンパイル・実行しているだけだ。
val変数の配列とvar変数の配列?
p.38
val変数の配列を宣言して値を定義する方法は複数あります。最初の方法は、newを使ってArrayオブジェクトを作成し、後から各要素の値を指定する方法です。
(snip)
p.41
val変数と同様に、var変数の配列を宣言して値を定義する方法も複数あります。最初の方法は、newを使ってArrayオブジェクトを作成し、後から各要素の値を指定する方法です。
(snip)
いや、配列が代入される先がvalだろうがvarだろうが配列を生成・初期化する方法は同じに決まってるでしょうに。なんで全く同じこと二回繰り返すの?
中置演算子の後に改行を置くとエラー?
p.45
なお、括弧を使わずに、「2 + (ここで改行)3」というように、演算子(この場合、+は2個の値の間に置く演算子なので中置演算子という)の後で改行しようとすると、次のようにエラーになります。
scala> 2 + <console>:5: error: ambiguous reference to overloaded definition, both method + in class Int of type (Double)Double and method + in class Int of type (Float)Float match expected type ? 2 + ^
前のエントリでも書いたのだが、REPLでしか確認してないのか?普通に以下のようにファイルに書いてscalaコマンドで実行すれば、実行できる。
2 + 3
REPLは改行の扱いに関して通常のscalaプログラムに比べて制限が厳しいのだから、通常のscalaプログラムで確認してから言って欲しいところだ。
「式に代入する」?
p.90
また、次のように式に代入することがよくあります。
var x = 1 + 2
さすがにこれは単なるtypoだろうと思うが、式に代入じゃなくて変数に代入だよね。
しかし、この著者、必要ないところでやたらvar使ってる気がするがこれはどうなんだろうか。
犬の種類で継承を説明
p.107
わかりやすい例で例えると、まず、「ワンと吠える四足の動物」を「犬クラス」として定義したものとします。この「犬クラス」は、どんな種類の犬であるかという点が未確定の、定義がややあいまいなクラスです。
間違いではないけど、いい加減この手のワンとかニャーで継承を説明するのは止めた方がいいんではないかと。わかってる人なら説明は要らないし、わからない人がこれでわかるようになるとも思えない。
Listがイミュータブルなデータ構造であることがスルーされている
p.134
Listは値を保存するクラスです。配列を保存するArrayに似ていますが、単に値を保存するだけのArrayとは違って、要素のソート(sort)や逆転(reverse)、要素の削除(remove)など、さまざまな操作が可能です。
Arrayでもsortとかはできるよ、というのはおいといて、Arrayとの大きな違いである、イミュータブルなデータ構造である事をスルーしてどうするんだと。
何故Random?
pp.138-140にRandomクラスの説明があるのだが、他にOptionとかTupleとか重要なクラスはあるだろうに、それらを差し置いて、何故Randomの説明?
付録A Scalaのシンプルリファレンスについて
無名関数の仮引数の型は省略できない?
p.176
(snip)
典型的には関数リテラルを定義するときに使います。たとえば、式Equの結果の値を返すfncnameという名前の関数を定義します。関数の引数paramnの型Tnは省略できません。
省略できない場合もあるけど、関数引数として現れる場合など、一般的な使い方では型推論によって省略可能であるケースがたくさんあるだろうに。
_は全てのパターンに一致する?
p.176
(snip)
[解説] パターンマッチですべてのパターンに一致させます。[例1]次のインポート文の最後の「_」は、scala.swing.eventにあるすべてのクラスをインポートすることを意味します。
(snip)
p.177
[例2]次の式は、変数iの値が0であるときには「Zero」を返し、1であるときには「One」を返し、それ以外の値であるときには「Other」を返します。
(snip)
いやいや、[例1]と[例2]の「_」は全く別の意味だから。なんでそこを混同するのだろう。
単項+は二項演算子の+の省略形?
p.177
[書式]
(expr1) + expr2[解説]expr1とexpr2を加算します。式が文字列の場合は、文字列を結合します。expr1を省略したときには、expr2を正の数として評価します。
いや、単項+と二項演算子の+は全く別物でしょ。なんでいっしょくたにしてるの?単項-についても同様の解説があったが、面倒なので省略。
配列の代入は特別?
[書式]
Exp = new Array[ type ]( n ) Exp = Array( val1, val2, ..)
配列は普通のオブジェクトなわけで、それを変数に代入したりできるのは当たり前。なんでわざわざ配列の変数への代入を特別扱いしてるのかさっぱりわからない。ちなみに、Boolean/Byte/...などのプリミティブ型についても個別に項目を設けて変数の宣言の仕方を書いてある。はっきり言ってページの無駄。
matchの解説が重複している
p.190
[書式]
expr match { case v1 => Value1 case v2 => Value2 case v3 => Value3 . . case _ => DefaultValue }
p.198
[書式]
expr match { case v1 => Value1 case v2 => Value2 case v3 => Value3 . . case _ => DefaultValue }
ほぼ全く同じ解説が2箇所に分散して存在している意味がわからん。
for...yieldの結果は配列?
p.210
[書式]
expr1 ::= for (( Enumerators | { Enumerators }) {nl} yield expr[解説]forループの中で繰り返して評価された値を、結果の型の配列に保存します。この配列が、for式の値になります。
なんで配列限定?ListだろうがOptionだろうがflatMapやmapメソッドを持っているものならなんでも使えるでしょ。まあ、mapメソッドなどについては説明されてなかったから、そもそもそのような説明の仕方はできないだろうという話もあるが。
付録F 参考リソースについて
Scalaの公式サイトやコップ本を挙げているのは良いが、Scalaにはあまり関係無いと思われる自著を3冊も挙げているのは何故?
*1:もちろん、構文エラーである