Creative Scala (日本語版)

Dave Gurnell, Noel Welsh 著 Eugene Yokota 訳

May 2018

序文

Creative Scala は、Scala 歴ゼロのデベロッパー向けに書かれています。 関数型プログラミングを楽しく学べることを目指しました。 あなたが他のプログラミング言語の初歩は少しかじったことがあることを前提としますが、Scala やその他の関数型言語には慣れ親しんでいないことを想定しています。

この本の 3つの目標は:

  1. 関数型プログラミングを紹介して、あなたがプログラムを計算したり筋道を立てて推論できるようになることで、他の関数型プログラミングに関する入門書に進めるようになること。
  2. Scala を使って自分の興味のあることに探検していくのに十分な Scala を教えること。
  3. これらを 2次元コンピューターグラフィックスを使って楽しく、優しく、興味深い方法で提供すること。

この 3点です。

私たちの動機は自分たちがプログラミングを学んだり、関数型プログラミングを勉強したり、商用デベロッパーに Scala を教えてきた経験から来ています。

まず、私たちは、関数型プログラミングが未来であることを信じています。 プログラミング経験が浅いことを前提としているので、関数型プログラミングとあなたが経験したことがあるかもしれないオブジェクト指向プログラミングの違いの詳細はここでは割愛させていただきます。 コンピュータのプログラムについて考えたり書いたりするにはいくつの方法があり、私たちは関数型プログラミングという方法を選んだとだけ言っておきましょう。

関数型プログラミングを選んだ理由のほうが興味深いと思います。 プログラミングを教えるのにありがちな方法として私たちが「構文のごった煮」方式と呼んでいるものがあります。 この方式ではプログラミング言語は、(変数、for ループ、while ループ、メソッドなど) 構文機能の集合として教えられ、いつどの機能を使うのかは生徒に任せられます。 大学生としてプログラミングを習ったり、社会人としてプログラミングを教える立場になった両方の場合において私たちはこの方式が失敗するのを見てきました。生徒が問題をコードに体系的に分解するすべを持たないからです。 拙い授業の結果として多くの生徒が脱落する結果となりました。 残った生徒は、私たちのように既に広いプログラミング経験を持つ人が多かったと思います。

小学校の算数の時間の筆算の足し算のことを思い出してください。 これは暗算で足すには数が大きい場合に数字を足すための基本の方法です。 なので、例えば 266 + 385 を足すには桁をそろえて書き出して、10 を超えたら繰り上げを行うなどといった具合です。 算数は好きな授業じゃなかったかもしれませんが、いくつかの重要な教訓が隠されています。 第一は、問題を解くための体系的な方法が与えられたということです。 問題が筆算の足し算で解けると気がつけば、答えを計算することができます。 第二点は、筆算の足し算を使うのになぜ正しいのかを理解している必要は無いということです (知っていることに越したことは無いですが)。 手順さえ正しく従えば、正しい答えを得ることができます。

関数型プログラミングの優れているのは、それが筆算の足し算のようになっていることです。 私たちは、正しく従えば正しく答えを得ることが保証されているレシピをいくつか持っています。 私たちは、これをプログラムを計算すると言います。 これは、プログラミングに創造性が欠けると言っているわけではありません。しかし、問題の構造をよく理解するのがチャレンジであって、それができたらすぐにレシピを使うことができます。 コードそのものは興味深い部分では無いのです。

私たちは Scala を使って関数型プログラミングを教えますが、Scala そのものを教えるわけではありません。 Scala は今需要がある言語です。 Scala プログラマーは様々な産業において比較的簡単に職を探すことができ、それは Scala を習うための重要な動機となります。 Scala の人気の理由の 1つとして Scala がオブジェクト指向プログラミングという古いプログラミングの方法と関数型プログラミングの 2つをまたいでいる言語だということが挙げられます。 多くのコードがオブジェクト指向スタイルで書かれていて、そのスタイルに慣れ親しんだプログラマーも多くいます。 Scala は、オブジェクト指向プログラミングから関数型プログラミングへ緩やかに移行する方法を与えてくれます。 しかし、これは Scala が大きな言語であることも意味し、オブジェクト指向の部分と関数型の部分の相互作用には分かりづらいこともあります。 私たちは、関数型プログラミングの方がオブジェクト指向プログラミングよりもずっと効果的で、新しいプログラマーが同時にオブジェクト指向のテクニックを教えて混乱させる必要は無いと思っています。 それは、後からでも遅くはないと思います。 そのため、この本では完全に Scala の関数型プログラミングの部分だけを使うことにします。

私たちは、関数型プログラミングと Scala を探検するにあたって、楽しんでもらえるを願ってコンピューター・グラフィックスという方法を選びました。 Scala には多くの入門書がありますが、多くの例題はビジネスや数学に関するものです。 例えば、とても人気な Coursera コースの初めの練習問題は、指示関数を用いて集合を実装するというものです。 あなたがそういうコンセプトに直接取り組みたいタイプの人ならば、そういうコンテンツは既にいっぱいあると思います。 私たちは別のグループを対象としようと思いました。数学はちょっと苦手だけども、ビジュアル・アートなら興味があるかなという人たちです。 正直に言っておくと、この本にも数学は出てきますが、そのコンセプトが何故必要なのかを動機づけ、ヴィジュアル化することで、怖さを軽減できたことを願っています。

この本は Scala を使いこなせるようになるための基礎となるメンタルモデルを提供しますが、自立できるための Scala の全てをここでカバーできるわけではありません。 更に Scala を習うためには、他の Scala の教科書の中から良いものを選んで進むことをお勧めします。 拙著の Essential Scala も検討してみてください。

練習問題を一人で解いているならば、Gitter chat room に参加して分からないことを聞いたり、この本の感想をシェアしてみてください。

Creative Scala のテキスト、及び練習問題で使われるお絵かきライブラリ Doodle は全てオープンソースです。 コードは私たちの GitHub アカウントにて公開しています。 英語版にコントリビュートしたい方は Gitter もしくは email で連絡してください。

ダウンロードしてくれてありがとう。これから creative プログラミングを始めましょう!

—Dave と Noel

Early access 版に関する注意

これは Creative Scala のベータ版です。 本文内や練習問題にタイポやその他の間違いがあるかもしれません。

日本語版に関する間違いの報告は eed3si9n/creative-scala の issues を使ってお願いします。

英語版に関する間違いの報告は Gitter chat room もしくは email でお願いします:

謝辞

Creative Scala (英語版) は Dave GurnellNoel Welsh によって書かれました。Richard Dallaway さん、Jonathan Ferguson さん、そして Underscore のみんなが何度も校正を行ってくれたことを感謝します。

間違いを指摘してくれたり、この本を改善するための意見をくださった多くの方々にもこの場をかりて感謝したいと思います: Neil Moore さん; Kelley Robinson さん、Julie Pitt さん、その他の ScalaBridge オーガナイザー; d43 さん; Matt Kohl さん; Alexa Kovachevich さんなど ScalaBridge その他のイベントで Creative Scala を読み進めた生徒の方々; その他直接コメントや意見をくれた多くの素晴らしい Scala コミュニティーの面々。Bridgewater 社、特に Lauren Cipicchio さんが、もしすると知らずに Creative Scala 第二版の初期開発に投資していただき、初期の生徒を提供していただいたことをここで感謝したいと思います。

最後に Creative Scala がプログラミング言語理論とコンピューターサイエンス教育にたずさわる多くの研究者の成果のおかであることをここに書きたいと思います。特に私たちが強調したいのは:

1 始めてみよう

まず最初のステップは Creative Scala の作業に必要なソフトウェアをインストールすることです。ここでは 2つの道のりを解説します:

  1. テキストエディタとターミナルを使う方法。プログラミングを一切やったこと無い人にはこの方法をお勧めします
  2. IntelliJ IDEA を使う方法。IDE を使うのに慣れている人やターミナルを使うのが不安な人にはこの方法をお勧めします。

もしも経験を積んだデベロッパーで自分の好みのセットアップがある場合はそのままそれを使って、必要に応じて以下の手順を調整して使ってください。

何を言っているのかさっぱりという人は、この章でこれから説明していくので読み進めてください。

1.1 ターミナルとテキストエディタのインストール

この節では、プログラミングが初めてという人のために私たちがお勧めする、ターミナルとテキストエディタを使った Creative Scala のセットアップ方法を解説します。 インストールするものは:

1.1.1 macOS

ターミナルを開く。(ツールバー右側の虫めがねアイコンに “terminal” と打ち込んでください。)

Java をインストールします。 ターミナルに以下を打ってください:

java

もしもこれが実行されれば Java はインストール済みです。 インストールされていなければ、Java をインストールする画面が表示されます。

Homebrew をインストールします。 以下をターミナルにペーストしてください:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Homebrew を使って git をインストールします。 ターミナルに以下を打ち込んでください:

brew install git

次にテキストエディタ Atom をインストールします。 ターミナルに以下を打ち込んでください:

brew install Caskroom/cask/atom

Atom 内で Scala サポートをインストールしてください: Settings > Install > language-scala

これで Git を使って Creative Scala の作業をするための sbt プロジェクトを取得することができます。 以下を打ち込んでください:

git clone https://github.com/underscoreio/creative-scala-template.git

作業を共有する

代替のセットアップとして、先に Creative Scala template プロジェクトを fork して、それをあなたのコンピュータに clone するという方法があります。 これは、あなたが自分の作業結果を他の人とシェアしたい場合のセットアップで、例えばリモートのインストラクターと Creative Scala を受講している場合や、自分の作業を他の人にも見て欲しい場合に使うことができます。

このセットアップではまず Creative Scala template を fork します。 そしてあなたの fork を clone します。 この代替セットアップ方法に関してはこの章の後ほどの GitHub の節で解説します。

今作られたディレクトリに変えて、sbt を実行してみましょう。

cd creative-scala-template
./sbt.sh

sbt が起動したはずです。 sbt 内で console と打ち込んでみてください。 最後に、以下を打ち込んでください:

Example.image.draw

3つの円の画像が現れるはずです!

ここまでできれば、Creative Scala の作業をするのに必要なソフトウェアは全てインストールされました。

最後のステップは Atom を起動して、src/main/scala 内にある Example.scala を開くことです。

1.1.2 Windows

Java をダウンロードしてインストールします。 “JDK” (Java development kit) で検索してください。 Oracle のサイトが出てくるはずです。 ライセンスを承諾して、JDK をダウンロードしてください。 ダウンロードが完了したら、インストーラーを実行してください。

Atom をダウンロードしてインストールします。 https://atom.io/ へ行って、Atom の Windows版をダウンロードしてください。 ダウンロードが完了したら、インストーラーを実行してください。

Git をダウンロードしてインストールします。 https://git-scm.com/ へ行って、Git の Windows版をダウンロードしてください。 ダウンロードが完了したら、インストーラーを実行してください。 最後に Git を開くオプションが出てくるはずなので、それを選択してください。 コマンドプロンプト画面が開きます。 以下を打ち込んでください:

git clone https://github.com/underscoreio/creative-scala-template.git

作業を共有する

代替のセットアップとして、先に Creative Scala template プロジェクトを fork して、それをあなたのコンピュータに clone するという方法があります。 これは、あなたが自分の作業結果を他の人とシェアしたい場合のセットアップで、例えばリモートのインストラクターと Creative Scala を受講している場合や、自分の作業を他の人にも見て欲しい場合に使うことができます。

このセットアップではまず Creative Scala template を fork します。 そしてあなたの fork を clone します。 この代替セットアップ方法に関してはこの章の後ほどの GitHub の節で解説します。

コマンドプロンプトを開きます。 画面左下の Windows アイコンをクリックしてください。 検索ボックスに「cmd」と入力して、プログラムが出てきたらそれを実行してください。 開いたウィンドウ内で以下を打ち込んでください:

cd creative-scala-template

さっきダウンロードした Create Scala template プロジェクトのディレクトリに変わるはずです。 以下を打ち込んで sbt を起動してください:

sbt.bat

sbt 内で console と打ち込んでみてください。 最後に、以下を打ち込んでください:

Example.image.draw

3つの円の画像が現れるはずです!

ここまでできれば、Creative Scala の作業をするのに必要なソフトウェアは全てインストールされました。

最後のステップは Atom を起動して、src/main/scala 内にある Example.scala を開くことです。

1.1.3 Linux

macOS の手順に従って、Homebrew の代わりに使っているディストリビューションのパッケージマネジャーを使ってください。

1.2 IntelliJ

IntelliJ は、Scala や他のプログラミング言語のための統合開発環境 (IDE) です。これはいくつものプログラミングツールを 1つのアプリケーションにまとめたもので、Visual Studio や Eclipse など他の IDE に慣れている人にお勧めします。

まずは IntelliJ Scala Bundle をダウンロードしてください。

IntelliJ Scala Bundle を起動すると、Welcome to IntelliJ IDEA というダイアログが出てきて、Create New Project、Import Project、Open、Check out from Version Control という選択肢が表示されます。 「Check out from Version Control」を選択して、URL に https://github.com/underscoreio/creative-scala-template.git と入力して、「Clone」を選びます。 「Would you like to open it?」と聞かれたら「Yes」を選びます。

オプション画面が出てきたら「Use sbt shell for build and import」選択します。

画面左下の sbt shell に console と打ち込んでください。 (busy) > と書かれたプロンプトに以下を打ち込んでください:

Example.image.draw

3つの円の画像が現れるはずです!

:q

と打ち込んで console を終了します。

1.3 予備知識

この節では、これから私たちが使うツールの予備知識を解説します。 もしもあなたが経験を積んだデベロッパーであれば既に常識だと思うので読み飛ばしてください。 もしもそうじゃなければ、これから使うソフトウェアについての背景を知ることで役に立てばいいと思います。

1.3.1 ターミナル

コンピューターの黎明期には、今では一般的なユーザー・インターフェイスとなった、グラフィカルなウィンドウ、マウスで制御されるカーソル、そしてコンピューターの直接操作ということそのものが存在しませんでした。 その代りに、ユーザーは端末 (ターミナル) という装置にコマンドを打ち込んでコンピューターを操作しました。 直接操作の方が多くの場面において優れていますが、ターミナルを用いたコマンドライン操作の方が便利なこともあります。 例えば、data で始まるファイル名のファイルがどれだけの容量を占めているかを調べたいとするとき Linux や macOS ならば以下のコマンドを実行することができます:

du -hs data*

これは 3つの要素に分解することができます:

これを直接操作系のインターフェイスを用いて行うのはより時間のかかる作業となるでしょう。

コマンドラインのほうが学習するのが難しいですが、代わりに非常に強力なツールを得ることができます。 私たちが使うターミナルの用法は限られているものなので、上の例が怖いなと思っても心配しないでください!

1.3.2 テキストエディタ

あなたは多分ワードプロセッサーで文章を書いたことがあると思います。 ワードプロセッサーはテキストを書いて、(最近見ることが減ってきた) 印刷されたページでのフォーマットの制御を行うことができます。 ワードプロセッサーは、文章の作成に便利なスペルチェッカーや目次の生成などといった強力なコマンドを持ちます。

テキストエディタはコードを書くためのワードプロセッサーです。 ワードプロセッサーがテキストの視覚的なプレゼンテーションにこだわるように、テキストエディタはプログラミングに特化した多くの機能を持ちます。 強力な検索置換機能、そしてプロジェクト内の多くの異なるファイル間へ素早くジャンプできるための機能などが典型的な例です。

テキストエディタは端末が使われていた時代にさかのぼるため、驚くことに当時から使われ続けているツールがいくつかあります。 古来から続いており、今も現役で活躍している 2大エディターとして Emacs と Vim があります。 私は Emacs を約20年使い続けているため、Emacs が全ての存在しうるテキストエディターの中で最も偉大なものであり、Vim ユーザーは悪趣味と劣等なツールに呪われてしまった脳みそが筋肉の人たちであることを本能的に分かっています。 Vim ユーザーは私のことを同じように思っているでしょう。

Vim と Emac のユーザーが一丸となることがあるとすれば、それは今流行りの Sublime Text や Atom といったテキストエディタは人類文明の没落を招いているということです。 しかしながら、初めてのテキストエディタとしては Atom をお勧めします。 Vim と Emacs の両方共現在広く使われているユーザーインターフェイスが確立する前に作られてたため、使いこなすのにかなり癖があるからです。

1.3.3 コンパイラ

私たちがテキストエディタで書くコードは、そのままではコンピュータが実行することができません。 コンパイラはコードをコンピュータが実行できる形式に翻訳します。 その翻訳を行う途中で、いくつかコードの検査も行います。 この検査が通らない場合はコードはコンパイルされずに、コンパイラは代わりにエラー・メッセージを表示します。 コンパイラが何をチェックすることができて、何ができないのかはまた後ほど見ていきます。

コンパイラがコンピュータが実行できる形に翻訳すると上で言いましたが、Scala に関して言うと実はこれは完全な真実ではありません。 コンパイラが出力するのはバイトコードと呼ばれるもので、Java Virtual Machine (JVM) という別のプログラムがこのコードを実行します1

1.3.4 総合開発環境 (IDE)

総合開発環境 (IDE) はテキストエディタ、コンパイラ、その他のプログラミング用のツールを一つのプログラムにまとめたものです。 IDE に絶対的な信用をおいている人たちもいれば、ターミナルとテキストエディタを好む人もいます。 プログラミングに初めての人へ私たちがお勧めするのはターミナルとテキストエディタを使う方法です。 IDE に慣れているならば、現在 Scala 開発に最も適している IDE は IntelliJ IDEA です。

1.3.5 バージョン管理

バージョン管理は私たちが使うツールの 1つです。 バージョン管理システムは、グループ化された複数のファイルに対する全ての変更を記録するためのプログラムです。 プロジェクトにおいて、複数の人が同時に作業できるようにするのは非常に便利ですが、そのときにバージョン管理を使うことで間違ってお互いの変更が上書きされないことを保証します。 Creative Scala を行うにあたってバージョン管理はさして重要なことではありませんが、早めにバージョン管理に触れておくのは良いことだと思います。

私たちが使うバージョン管理は Git です。 これは強力ですが、複雑なものです。 幸い今回はあまり Git に関して習う必要はありません。 私たちの Git の用例の多くは、Git に保存したソフトウェアを共有するための GitHub というウェブサイト経由で行うのがほとんどです。 Creative Scala で使われるソフトウェアは GitHub で共有されています。

1.4 GitHub

Creative Scala の作業をするのに必要なコードを全てセットアップした[テンプレート]を用意しました。 このテンプレートは、コードを共有するためのウェブサイトである GitHub に保存されています。

このテンプレートをあなたのコンピュータにコピーすることができ、Git はこれを clone と呼んでいます。 しかし、この方法ではあなたが行う変更を GitHub へ保存し直して、他の人が見れるようにはなりません。

もしもあなたが行う変更を他の人と共有したい場合は、テンプレートプロジェクトを自分の GitHub へコピーする必要があります。 Git はこれを fork と呼んでいます。 GitHub 上でまずレポジトリを fork して、あなたの fork を手元に clone してください。 この方法を行えば、自分の変更点を GitHub のあなたの fork へ保存し直すことができます。

このプロセスを始めるには、GitHub アカウントを作成する必要があります。アカウントを持っていない人は作ってください。

アカウントができたら、ブラウザ上からテンプレートプロジェクトへ行きます。 上の右側に “Fork” と書かれたボタンがあります。 このボタンを押して、自分のテンプレートプロジェクトを作成します。 テンプレートの自分の fork を表示するウェブページに飛ばされるはずです。 このリポジトリの名前を覚えておいてください。GitHub のユーザー名が yourname さんだとすると、yourname/creative-scala-template みたいな感じになると思います。

あなたの fork を clone するには、以下のコマンドの yourname の部分を GitHub ユーザー名に置き換えて実行するだけです。

git clone git@github.com:yourname/creative-scala-template.git

これで、あなたが行う変更を GitHub 上のあなたの fork へ送ることができるようになりました。 Git を使ってこれを行うのは少し複雑です。 何らかの変更を行ったあと以下を実行する必要があります:

以下は、コマンドラインからこれを実行する一例です。

git add
git commit -m "Explain here what you did"
git push

GitHub は、GitHub Desktop という Git を使うための無料のグラフィカルなツールを作っています。 Git を始めたばかりのときはこれが一番分かりやすい方法でしょう。

2 式、値、型

Scala のプログラムはという 3つの基礎要素から成り立っています。この節では、これらの概念を考察します。

非常にシンプルな式の一例です:

1 + 2

は Scala コードの断片です。式はテキストエディタに書くこともあれば、紙に書いたり、壁に書くこともできます。

式は作文に似ています。作文が世界に作用を持つためには誰かが読む必要があるのと同様 (ということは読者が書かれた言語を理解している必要もあります)、式が作用を持つにはコンピュータが式を実行する必要があります。式を実行した結果がです。値は、コンピューターのメモリの中に生きていて、それは作文を読んだ結果が読者の頭の中に生きているのと同様です。式を値に変換するプロセスを指して、式を評価すると言ったり、実行すると言ったりもします。

console に式を書いて「Enter」(もしくは 「Return」) を押すことで即時に式を評価することができます。今すぐ試してみてください。

1 + 2
// res1: Int = 3

console は、式を評価した値と式の型を返します。

1 + 2 という式は 3 という値に評価されます。私たちはこのページに数字の 3 と書くことができますが、真の値はコンピューターのメモリに格納されたものです。この場合、これは 2の補数で表された 32ビット整数となります。「2の補数で表された 32ビット整数」の意味は重要なものではありません。このページや console に書かれた数字ではなく、3 という値のコンピューターの表現こそが真の値であることを強調するために書いたものです。

このパズルの最後のピースはです。プログラムを実行せずに決定できるものを型と言います。式 1 + 2Int 型を持ち、これはこの式が評価する値を整数だと解釈するべきことを意味します。これは、この式の結果を使って他の式を書くことができるけども、その式が整数に合った演算である必要があることを意味します。例えば、加算、減算、乗算、除算などが可能ですが、整数を小文字に変換することはできません。

型は多くの場合において、私たちが値 (コンピューターのメモリにある「あれ」) をどう理解するべきかを教えてくれます。整数として理解するべきなのか、マウスの現在位置を表す点のストリームとして理解するべきでしょうか? それは型が教えてくれます。私たちは型を、実行時に表現を持たないものを含む他のことにも使うことができます。これらの用法はここで深入りするには少し難易度が高いですが、型が値に対応すると思ってしまうのは間違いです。Scala では型はコンパイル時のみに存在します。任意の値があるとき、その値を生成した式の型の表現は実行時にはありません。

Scala プログラムが実行する前に、それはコンパイルされる必要があります。コンパイルは、プログラムのつじつまが合うかの検査を行います。例えば、(1 + 2) は構文的に正しいけども、(1 + 2 は正しくありません。プログラムは、型検査も通過する必要があります。型検査は、行おうとしている演算が今ある型に対して正しいかの検査です。1 + 2 は型検査を通りますが (整数の加算をしています)、1.toUpperCase は通りません (数字に大文字も小文字もありません)。

コンパイルに成功したプログラムだけを実行することができます。コンパイルは、作文の文法のようなものと考えることができます。例えば、 「F$Rf fjrmn;l df.fd」は英文法的に間違っています。文字の並びは単語を形成していません。「dog fly a here no」という文は妥当な単語から成るけども、その並びは英文法を違反します。これは Scala が型検査を行うのと似ていると思います。

コードがコンパイルされる時のことをコンパイル時、コードが実行される時のことを実行時と言います。

2.1 リテラル式

Scala の様々な式を探検してみましょう。初めは、最もシンプルな式であるリテラルです。 リテラル式の具体例です:

3
// res0: Int = 3

リテラルは「それ自身」に評価されます。式の書き方と console が値を表示する方法は一緒です。ただし、値の書かれた表現とコンピューターのメモリ内の実際の表現には違いがあることを思い出してください。

Scala には多くの異なる形のリテラルがあります。私たちは既に Int リテラルを見ました。浮動小数点数という別の型があり、これは別のリテラル構文で表されます。これは、コンピューターの実数に対する近似値に対応します。具体例を見てみましょう:

0.1
// res1: Double = 0.1

見ての通り、この型は Double と呼ばれます。

数字はいいとして、テキストはどうするのでしょうか。Scala の String 型は文字の列を表します。文字リテラルはダブルクォートで内容を囲んで書きます。

"To be fond of dancing was a certain step towards falling in love."
// res2: String = To be fond of dancing was a certain step towards falling in love.

数行にまたがった文字列を書きたいことがあります。これは、以下のようにトリプルダブルクォートを使って書きます。

"""
A new, a vast, and a powerful language is developed for the future use of analysis,
in which to wield its truths so that these may become of more speedy and accurate
practical application for the purposes of mankind than the means hitherto in our
possession have rendered possible.

  -- Ada Lovelace, the world's first programmer
"""
// res3: String =
// "
// A new, a vast, and a powerful language is developed for the future use of analysis,
// in which to wield its truths so that these may become of more speedy and accurate
// practical application for the purposes of mankind than the means hitherto in our
// possession have rendered possible.
// 
//   -- Ada Lovelace, the world's first programmer
// "

String は文字の列です。文字それぞれにも Char という型があって、それはシングルクォートを使って書かれます。

'a'
// res4: Char = a

最後に、イギリスの論理学者 George Boole にちなんで名付けられた Boolean 型のリテラル表現を見ていきましょう。気取った名前ですが、値が truefalse であるかというだけの意味で、ブールリテラルはそのまま以下のように書かれます。

true
// res5: Boolean = true

false
// res6: Boolean = false

リテラル式を使って値を作ることができるようになりましたが、何らかの方法で値と関わりを持つことができなければ何もできません。これまでに 1 + 2 というような複合式は見てきました。次の節ではオブジェクトとメソッドを習って、このような式やもっと面白い式が動作しているのかを理解します。

2.2 値はオブジェクトである

Scala において全ての値はオブジェクトです。オブジェクトは、データとそのデータに関する演算をグループ化したものです。例えば、2 はオブジェクトです。このデータは整数の 2 で、演算は慣れ親しんだ +、- などといったものです。オブジェクトの持つ演算をオブジェクトのメソッドと呼びます。

2.2.1 メソッド呼び出し

メソッドを呼び出すことでオブジェクトと関わりを持つことができます。例えば、toUpperCase メソッドを呼び出すことで String 値を大文字にしたものを得ることができます。

"Titan!".toUpperCase
// res0: String = TITAN!

メソッドの中にはパラメータ (引数と呼ばれることもあります) を受け取って、メソッドがどう動くかを制御できるものもあります。例えば take メソッドは String 値からいくつかの文字を取り出します。それが何文字なのかを take にパラメータを渡して指定する必要があります。

"Gilgamesh went abroad in the world".take(3)
// res1: String = Gil

"Gilgamesh went abroad in the world".take(9)
// res2: String = Gilgamesh

メソッド呼び出しは式の一つであり、オブジェクトへと評価されます。そのため、メソッド呼び出しを連鎖させて、より複雑なプログラムを書くことができます:

"Titan!".toUpperCase.toLowerCase
// res3: String = titan!

メソッド呼び出しの構文

メソッド呼び出しの構文は

anExpression.methodName(param1, ...)

または

anExpression.methodName

で、

  • anExpression は (オブジェクトに評価される) 任意の式で
  • methodName はメソッド名で
  • 省略可能な param1, ... は 1つもしくは複数の式で、メソッドのパラメータとして評価されます。

2.2.2 演算子

全ての値はオブジェクトで、メソッドは object.methodName(parameter) という構文で呼び出すと言いました。だとすると、1 + 2 という式はどのように説明したらいいでしょうか?

Scala において、a.b(c) と書ける式は a b c と書くことができます。そのため、以下の式は等価です:

1 + 2
// res4: Int = 3

1.+(2)
// res5: Int = 3

1つ目のメソッド呼び出しの書き方は、演算子スタイルと呼ばれます。

演算子中置記法

a.b(c) と書ける任意の Scala の式は a b c と書くことができます。

a b c d e は、a.b(c).d(e) と等価であり a.b(c, d, e) ではないことに注意してください。

2.3

より複雑な式が書けるようになった所で、型に関してもう少し話すことができます。

型の用例の 1つとして存在しないメソッドの呼び出しを防止するというものがあります。コンパイラが式を値に評価するとき、式の型はどのメソッドが存在するのかをコンパイラに教えてくれます。存在しないメソッドを呼び出そうとしてもそれはコンパイルされません。以下に簡単な具体例を使って説明します。

"Brontë" / "Austen"
// <console>:13: error: value / is not a member of String
//        "Brontë" / "Austen"
//                 ^

1.take(2)
// <console>:13: error: value take is not a member of Int
//        1.take(2)
//          ^

本当に、式の型がどのメソッドを呼び出せるのかを決定します。これはより複雑な式に対してメソッドを呼び出すことで実験することができます。

(1 + 3).take(1)
// <console>:13: error: value take is not a member of Int
//        (1 + 3).take(1)
//                ^

この型検査の処理は、メソッドのパラメータにも適用されます。

1.min("zero")
// <console>:13: error: type mismatch;
//  found   : String("zero")
//  required: Int
//        1.min("zero")
//              ^

型は式の属性で (以前にも話した通り) コンパイル時に存在します。そのため、実行時に式を評価した結果がエラーとなっても、式の型は決定することができます。例えば、Int をゼロで割ると実行時エラーが発生します。

1 / 0
// java.lang.ArithmeticException: / by zero
//   ... 43 elided

1 / 0 はそれでも型を持ち、以下のようにして console で表示させることができます。

:type 1 / 0
// Int

実行時に失敗するサブ式を含んだ複合式を書くこともできます。

(2 + (1 / 0) + 3)
// java.lang.ArithmeticException: / by zero
//   ... 43 elided

そして、この式も型を持ちます。

:type (2 + (1 / 0) + 3)
// Int

2.4 練習問題

2.4.0.1 算数

整数リテラル、加算、減算の全てを使って 42 に評価される式を書いてみよう。

この練習問題は Scala のコードを書くのに慣れるためのものです。色々な解答が可能ですが、1つの例として以下のように書けます。

1 + 43 - 2
// res0: Int = 42

2.4.0.2 文字列の追加

++ メソッドを使って 2つの文字列を連結してみよう (文字列の追加とも言います)。適当な式を普通のメソッド呼び出しスタイルと演算子スタイルそれぞれを使って書いてみよう。

こんな感じでいいと思います。

"It is a truth ".++("universally acknowledged")
// res1: String = It is a truth universally acknowledged

"It is a truth " ++ "universally acknowledged"
// res2: String = It is a truth universally acknowledged

2.4.0.3 優先順位

数学では演算子には優先順位があることを習いました。例えば、1 + 2 * 3 という式があるとき、乗算を先に行ってから加算を行います。Scala でもこのルールが当てはまるでしょうか?

console で色々実験してみると、Scala も標準的な演算子の優先順位に従うことが分かると思います。以下にこれを示す例を挙げます。

1 + 2 * 3
// res3: Int = 7

1 + (2 * 3)
// res4: Int = 7

(1 + 2) * 3
// res5: Int = 9

2.4.0.4 型と値

以下のうち、コンパイルに失敗する式はどれでしょうか? もしくはコンパイルできるけれども、実行に失敗する式はどれでしょうか? コンパイルにも実行にも成功する式の評価値の型は、それぞれ何になるでしょうか?

1 + 2

"3".toInt

"Electric blue".toInt

"Electric blue".take(1)

"Electric blue".take("blue")

1 + ("Moonage daydream".indexOf("N"))

1 / 1 + ("Moonage daydream".indexOf("N"))

1 / (1 + ("Moonage daydream".indexOf("N")))
1 + 2
// res14: Int = 3

この式は Int 型を持ち、3 と評価されます。

"3".toInt
// res15: Int = 3

この式は Int 型を持ち、3 と評価されます。

"Electric blue".toInt
// java.lang.NumberFormatException: For input string: "Electric blue"
//   at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
//   at java.lang.Integer.parseInt(Integer.java:580)
//   at java.lang.Integer.parseInt(Integer.java:615)
//   at scala.collection.immutable.StringLike.toInt(StringLike.scala:301)
//   at scala.collection.immutable.StringLike.toInt$(StringLike.scala:301)
//   at scala.collection.immutable.StringOps.toInt(StringOps.scala:29)
//   ... 43 elided

この式は Int 型を持ちますが、実行時に失敗します。

"Electric blue".take(1)

この式は String 型を持ち、"E" と評価されます。

"Electric blue".take("blue")
// <console>:13: error: type mismatch;
//  found   : String("blue")
//  required: Int
//        "Electric blue".take("blue")
//                             ^

この式はコンパイル時に失敗するため、型を持ちません。

1 + ("Moonage daydream".indexOf("N"))
// res19: Int = 0

この式は Int 型を持ち、0 と評価されます。

1 / 1 + ("Moonage daydream".indexOf("N"))
// res20: Int = 0

この式は Int 型を持ち、演算子の優先順位により (1 / 1) + -1 と評価され、0 となります。

1 / (1 + ("Moonage daydream".indexOf("N")))

この式は Int 型を持ちますが、ゼロによる除算のため実行時に失敗します。

2.4.0.5 浮動小数点数の弱点

Double を紹介したとき、それは実数の近似値だと言いました。これは何故だと思いますか? ⅓ や π といった数を表すことを考えてみましょう。それらの数を 10進数で表すとしたらいくら容量が必要でしょうか?

Double が近似値であるのは、コンピューターの有限なメモリーに収める必要があるからです。1つの Double は、64ビットの容量を取り、これは多くの桁数を保持するのに十分ですが、π のように無限に展開する数を格納することはできません。

⅓ という数も 10進数では無限に展開します。Double は 2進数で格納されているため、10進数なら有限の桁数で表せる数でも 2進数では有限の表現を持たない場合があります。実は、0.1 はそのような数の一例です。

一般的に、浮動小数点数が実数と同じように振る舞うと期待すると思わぬ所でひどい目に合わされます。Creative Scala を使うには事足りますが、浮動小数点数を使って帳簿管理ソフトを書いてはいけません!

2.4.0.6 式の先にあるもの

今の計算モデルは、式 (プログラムのテキスト) とその型、そしてそれが評価されたときの値 (コンピューターのメモリ内に存在するもの) という 3つの要素を持ちます。これははたして十分なものでしょうか? このモデルを使って株式市場やコンピューターゲームを書くことができるでしょうか? このモデルを拡張する方法を考えてみてください。(これは自由回答の質問です。)

現行のモデルを拡張するいくつかの方法を考えることができます。

役に立つプログラムを書くには作用 (effect)、つまりコンピューターのメモリを超えた世界に関する何らかの変更を引き起こす能力を必要とします。例えば、画面に何か表示したり、音を出したり、他のコンピューターにメッセージを送信したりなどの作用があります。console は値を画面に表示することで自動的に何らかの作用を引き起こしてると言えます。より役に立つプログラムを書くには、それ以上の作用が必要になります。

また、私たちは今の所独自のオブジェクトやメソッドを定義したり、プログラムの中で値を再利用するすべを持ちません。例えば、プログラム中で誰かの名前を使いたいとすると、その名前を何度も繰り返して書く必要があります。抽象化の方法が必要で、これからその方法を見ていきます。

3 絵を使った演算

これまで数、文字列、その他の簡単なオブジェクトを使った演算をみてきました。これらは特に面白いものではありません。ここからは絵を使った計算に注目して、後ほどアニメーションをみていきます。絵を使うことは、より直接的にクリエイティブな機会を与えてくれます。自分たちのプログラムからリアルなアウトプットが出てくる感覚は他の方法では得られないことです。

私たちはグラフィックスを作るのに Doodle というライブラリを使います。この章では Dooble の初歩を習います。

Doodle 内の sbt console を使って練習問題を実行すると普通に動作すると思います。そうじゃない場合で Dooble を使うときは、コードに始めに以下の import 文を書く必要があります。

import doodle.core._
import doodle.core.Image._
import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

3.1 イメージ

まずは以前のように console を使って、簡単な形を描いてみましょう。

Image.circle(10)
// res0: doodle.core.Image = Circle(10.0)

何が起こっているのでしょう? Image はオブジェクトで、circle はそのオブジェクトのメソッドです。私たちは circle10 というパラメータを渡して、私たちが作る円の半径を指定します。結果の型に注目してください。Image となっていますね。

Doodle 内で console を実行した場合は、これらのイメージを作るためのメソッドが使用可能な状態になっているので、circle(10) とだけ書くこともできます。

circle(10)
// res1: doodle.core.Image = Circle(10.0)

この円を描画するためには、draw メソッドを呼びます。

circle(10).draw

fig. 1 のようなウィンドウが表示されるはずです。

Figure 1: A circle

Figure 1: A circle

Doodle は、円、長方形、三角形といったいくつかの基本図形をサポートします。長方形を描いてみましょう。

rectangle(100, 50).draw

結果は fig. 2 のようになります。

Figure 2: A rectangle

Figure 2: A rectangle

最後に、fig. 3 のような三角形を描いてみましょう。

triangle(60, 40).draw
Figure 3: A triangle

Figure 3: A triangle

練習問題

堂々巡り

1、10、100 単位の幅の円を作ってみましょう。次に、それを描いてみよう!

この練習問題は Dooble が正しくインストールされているかの確認を行い、このライブラリを使うのに慣れてもらいます。 Doodle を使うときの注意点として、イメージの定義イメージの描画が別であるということがあります。この点に関してはこの本を通して何回も出てきます。

以下のようなコードで円を作ることができます。

circle(1)
circle(10)
circle(100)

それぞれの円の draw メソッドを呼ぶことで円を描画することができます。

circle(1).draw
circle(10).draw
circle(100).draw

私のアートのタイプ

円の型は何でしょうか? 長方形の場合は? 三角形の場合は?

console で確認できる通り、それらは全て Image 型を持ちます。

:type circle(10)
// doodle.core.Image
:type rectangle(10, 10)
// doodle.core.Image
:type triangle(10, 10)
// doodle.core.Image

私のアートのタイプじゃない

イメージの描画の型は何でしょうか? これは何を意味するのでしょう?

再び console でこの質問を聞いてみましょう。

:type circle(10).draw
// Unit

見ての通り、イメージの描画の型は Unit です。Unit は特筆すべき値を返さない式のための型です。これは draw の場合に当てはまります。何故なら draw は画面に何かを表示するために呼ばれるのであり、戻り値に使い道は無いからです。Unit 型を持つ値は 1つだけあります。これは unit値と呼ばれ、リテラル式 () として書かれます。

console は unit値をデフォルトでは表示しないことに注意してください。

()

console に型を聞くことで、そこに unit値があることを確認することができます。

:type ()
// Unit

3.2 レイアウト

ここまでで、基本図形のイメージの作り方を見てきました。レイアウトメソッドを使ってイメージを組み合わせることで、より複雑なイメージを作ることができます。以下のコードを試してみましょう。fig. 4 のように、円と長方形が隣り合わせになっているのが見えるはずです。

(circle(10) beside rectangle(10, 20)).draw
Figure 4: A circle beside a rectangle

Figure 4: A circle beside a rectangle

tbl. 1 は、イメージを組み合わせるために Doodle が提供するレイアウトメソッドです。それぞれ使ってみて、何をするのか見てみよう。

Table 1: Doodle のレイアウトメソッド
演算子 説明 使用例
Image beside Image Image 2つのイメージを横に並べる circle(10) beside circle(20)
Image above Image Image 2つのイメージを縦に並べる circle(10) above circle(20)
Image below Image Image 2つのイメージを縦に並べる circle(10) below circle(20)
Image on Image Image 2つのイメージを中心で重ねる circle(10) on circle(20)
Image under Image Image 2つのイメージを中心で重ねる circle(10) under circle(20)

練習問題

The Width of a Circle

これまで見てきたレイアウトメソッドと基礎図形のイメージを使って fig. 5 のような絵を描いてみよう。

Figure 5: The width of a circle

Figure 5: The width of a circle

これは 3つの小さな円を 1つの大きな円に重ねたものなので、コードではこのように書けます。

(circle(20) beside circle(20) beside circle(20)) on circle(60)
// res0: doodle.core.Image = On(Beside(Beside(Circle(20.0),Circle(20.0)),Circle(20.0)),Circle(60.0))

3.3

レイアウトの他に、Doodle を使って私たちのイメージに彩りを与えることができます。tbl. 2 で解説するメソッドを試して、結果がどうなるか見てください。

Table 2: Doodle でイメージに色を付けるためのいくつかのメソッド
演算子 説明 使用例
Image fillColor Color Image 指定した色でイメージを塗りつ ぶす circle(10) fillColor Color.red
Image lineColor Color Image 指定した色で線画を描く circle(10) lineColor Color.blue
Image lineWidth Int Image 指定した筆幅で線画を描く circle(10) lineWidth 3

Doodle では、色を作るための方法がいくつかあります。 最もシンプルな方法は CommonColors.scala で定義済みの色を使うことです。 tbl. 3 によく使われる色を挙げます。

Table 3: 定義済みの色のうちよく使われるもの
使用例
Color.red Color circle(10) fillColor Color.red
Color.blue Color circle(10) fillColor Color.blue
Color.green Color circle(10) fillColor Color.green
Color.black Color circle(10) fillColor Color.black
Color.white Color circle(10) fillColor Color.white
Color.gray Color circle(10) fillColor Color.gray
Color.brown Color circle(10) fillColor Color.brown

練習問題

邪視の魔除け

邪視に対抗するための伝統的なお守りに似せた fig. 6 のようなイメージを作ってみよう。私は虹彩の部分は cornflowerBlue、外側の部分は darkBlue を使ったけども、独自に他の色も実験してみて!

Figure 6: 邪視退散!

Figure 6: 邪視退散!

これが私のお守りです:

((circle(10) fillColor Color.black) on
 (circle(20) fillColor Color.cornflowerBlue) on
 (circle(30) fillColor Color.white) on
 (circle(50) fillColor Color.darkBlue))
// res0: doodle.core.Image = On(On(On(ContextTransform(doodle.core.Image$$Lambda$4768/1796673738@3dd50473,Circle(10.0)),ContextTransform(doodle.core.Image$$Lambda$4768/1796673738@5dff3aed,Circle(20.0))),ContextTransform(doodle.core.Image$$Lambda$4768/1796673738@6eff56b4,Circle(30.0))),ContextTransform(doodle.core.Image$$Lambda$4768/1796673738@18757234,Circle(50.0)))

3.4 色の作成

ここまでで、イメージの中で定義済みの色を使う方法を見てきました。独自の色を作りたいとしたらどうすればいいでしょう? この節では、独自の色を作ったり、既存の色を変換して新しいものにする方法を解説します。

3.4.1 RGB カラー

コンピューターは、異なる量の赤、緑、青成分を混ぜて幅広い色を再現することで色を取り扱います。この RGB モデルは色の加法混合の一種です。赤、緑、青の成分はそれぞれ 0 から 255 の値を持つことができます。3つ全ての成分が最大値の 255 であるときは、純粋な白を得ることができます。全ての成分が 0 のときは黒となります。

Color オブジェクトの rgb メソッドを使って独自の RGB カラーを作ることができます。このメソッドは、赤、緑、青成分の 3つのパラメータを取ります。これらは UnsignedByte2 と呼ばれる 0 から 255 の数です。UnsignedByte には Int のようなリテラル式が無いため、Int から UnsignedByte へと変換する必要があります。これは、uByte メソッドを使って行うことができます。IntUnsignedByte よりも多くの値を取ることができるので、もしも数が UnsignedByte で表現するには小さすぎたり大きすぎる場合は 0 から 255 の範囲で最も近い値に変換されます。具体例で説明します。

0.uByte
// res0: doodle.core.UnsignedByte = UnsignedByte(-128)

255.uByte
// res1: doodle.core.UnsignedByte = UnsignedByte(127)

128.uByte
// res2: doodle.core.UnsignedByte = UnsignedByte(0)

-100.uByte // 小さすぎるので、0 に変換する
// res3: doodle.core.UnsignedByte = UnsignedByte(-128)

1000.uByte // 大きすぎるので、255 に変換する
// res4: doodle.core.UnsignedByte = UnsignedByte(127)

(UnsignedByte は Doodle の機能で、Scala が提供するものではないことに注意してください)

UnsignedByte の作り方が分かったところで、RGB カラーを作ってみましょう。

Color.rgb(255.uByte, 255.uByte, 255.uByte) // White
Color.rgb(0.uByte, 0.uByte, 0.uByte) // Black
Color.rgb(255.uByte, 0.uByte, 0.uByte) // Red

3.4.2 HSL カラー

RGB カラー表現は取り扱いが簡単ではありません。色相、彩度、明度 (HSL) のフォーマットの方が、私たちが色を認知するのにより近い形でモデル化されています。この表現法では色は以下の成分を持ちます:

fig. 7 は色相と明度を変えることでどう色が変化するかを示し、fig. 8 は彩度の変化の影響を示します。

Figure 7: 彩度を 1 に固定して、色相 (角度) と明度 (中心からの距離) の変化を示した色相環。

Figure 7: 彩度を 1 に固定して、色相 (角度) と明度 (中心からの距離) の変化を示した色相環。

Figure 8: 色相と明度を固定することで彩度の変化が色に与える影響を示した勾配。左端は彩度が 0 で右端が彩度が 1 となっている。

Figure 8: 色相と明度を固定することで彩度の変化が色に与える影響を示した勾配。左端は彩度が 0 で右端が彩度が 1 となっている。

Color.hsl メソッドを使って HSL 表現の色を作ることができます。このメソッドは、色相、彩度、明度をパラメータとして受け取ります。色相は Angle で、degrees メソッド (か radians メソッド) を用いて DoubleAngle へと変換することができます。

0.degrees
// res8: doodle.core.Angle = Angle(0.0)

180.degrees
// res9: doodle.core.Angle = Angle(3.141592653589793)

3.14.radians
// res10: doodle.core.Angle = Angle(3.14)

彩度と明度は 0.0 から 1.0 へと標準化されます。.normalized メソッドを使って Double を標準化された値へと変換します。

0.0.normalized
// res11: doodle.core.Normalized = Normalized(0.0)

1.0.normalized
// res12: doodle.core.Normalized = Normalized(1.0)

1.2.normalized // Too big, is clipped to 1.0
// res13: doodle.core.Normalized = Normalized(1.0)

-1.0.normalized // Too small, is clipped to 0.0
// res14: doodle.core.Normalized = Normalized(0.0)

これで HSL 表現を使って色を作ることができます。

Color.hsl(0.degrees, 0.8.normalized, 0.6.normalized) // A pastel red

この色を見るためには、絵の中で使ってみてください。例えば、fig. 9 を見てください。

Figure 9: パステルレッドを用いた三角形のレンダリング

Figure 9: パステルレッドを用いた三角形のレンダリング

3.4.3 色の操作

構図の効果は、実際に使われた色そのものだけではなく、色と色の間の関係よることが多いと思います。既存の色から新しい色を作ることができるメソッドがいくつかあります。特によく使われるのは:

具体例で解説すると

((circle(100) fillColor Color.red) beside
  (circle(100) fillColor Color.red.spin(15.degrees)) beside
    (circle(100) fillColor Color.red.spin(30.degrees))).lineWidth(5.0)

は fig. 10 を生成します。

Figure 10: Color.red から始まり、色相を 15度づつ回転させた 3つの円

Figure 10: Color.red から始まり、色相を 15度づつ回転させた 3つの円

次は似た例ですが、fig. 11 のように彩度と明度を操作します。

(((circle(20) fillColor (Color.red darken 0.2.normalized))
  beside (circle(20) fillColor Color.red)
  beside (circle(20) fillColor (Color.red lighten 0.2.normalized))) above
((rectangle(40,40) fillColor (Color.red desaturate 0.6.normalized))
  beside (rectangle(40,40) fillColor (Color.red desaturate 0.3.normalized))
  beside (rectangle(40,40) fillColor Color.red)))
Figure 11: 上の 3つの円は明度の変化を示し、下の 3つの四角形は彩度の変化を示す

Figure 11: 上の 3つの円は明度の変化を示し、下の 3つの四角形は彩度の変化を示す

3.4.4 透明度

アルファ値を与えることで、色に透明度を追加することができます。アルファ値 0.0 は完全な透明な色を表し、アルファ値 1.0 は完全に不透明な色を表します。Color.rgbaColor.hsla は、Normalized のアルファ値を表す 4つめのパラメータを受け取ります。既にある色に alpha メソッドを呼ぶことで異なる透明度を持つ新しい色を作ることができます。例えば、fig. 12 のようになります。

((circle(40) fillColor (Color.red.alpha(0.5.normalized))) beside
 (circle(40) fillColor (Color.blue.alpha(0.5.normalized))) on
 (circle(40) fillColor (Color.green.alpha(0.5.normalized))))
Figure 12: アルファ値 0.5 の円を使って透明度を表す

Figure 12: アルファ値 0.5 の円を使って透明度を表す

練習問題

類似色の三角形

3つの三角形を、三角に配置して、類似色で色付けしてみよう。類似色とは色相の近い色のことです。(ちょっと手のこんだ) 具体例 fig. 13 を見てください。

Figure 13: 類似色の三角形。色は darkSlateBlue のバリエーションとして選ばれています。

Figure 13: 類似色の三角形。色は darkSlateBlue のバリエーションとして選ばれています。

回答を console に書くにはちょっと長くなりすぎてきていますね。次にその対策を見ていきます。

((triangle(40, 40)
       lineWidth 6.0
       lineColor Color.darkSlateBlue
       fillColor (Color.darkSlateBlue lighten 0.3.normalized saturate 0.2.normalized spin 10.degrees)) above
  ((triangle(40, 40)
      lineWidth 6.0
      lineColor (Color.darkSlateBlue spin (-30.degrees))
      fillColor (Color.darkSlateBlue lighten 0.3.normalized saturate 0.2.normalized spin (-20.degrees))) beside
     (triangle(40, 40)
        lineWidth 6.0
        lineColor (Color.darkSlateBlue spin (30.degrees))
        fillColor (Color.darkSlateBlue lighten 0.3.normalized saturate 0.2.normalized spin (40.degrees)))))
// res19: doodle.core.Image = Above(ContextTransform(doodle.core.Image$$Lambda$6824/185365984@4bfaba7c,ContextTransform(doodle.core.Image$$Lambda$6826/1797463237@3e526236,ContextTransform(doodle.core.Image$$Lambda$6825/745609583@30274484,Triangle(40.0,40.0)))),Beside(ContextTransform(doodle.core.Image$$Lambda$6824/185365984@4391a371,ContextTransform(doodle.core.Image$$Lambda$6826/1797463237@5da6c825,ContextTransform(doodle.core.Image$$Lambda$6825/745609583@70c84275,Triangle(40.0,40.0)))),ContextTransform(doodle.core.Image$$Lambda$6824/185365984@7a413b8c,ContextTransform(doodle.core.Image$$Lambda$6826/1797463237@3485959e,ContextTransform(doodle.core.Image$$Lambda$6825/745609583@5f8994cb,Triangle(40.0,40.0))))))

3.5 練習問題

3.5.1 ターゲット

アーチェリー用のターゲットを、fig. 14 のように 3つの同心円で得点帯を表して描いてみよう。

Figure 14: シンプルなアーチェリー用ターゲット

Figure 14: シンプルなアーチェリー用ターゲット

ボーナス得点として、fig. 15 のようにターゲットを練習場に置けるようにスタンドを追加してください。

Figure 15: スタンド付きのアーチェリー用ターゲット

Figure 15: スタンド付きのアーチェリー用ターゲット

最もシンプルな解法は on 演算子を使って同心円を描くことです。

(circle(10) on circle(20) on circle(30))

ボーナス得点のスタンドは 2つの長方形を使って描きます。

(
  circle(10) on
  circle(20) on
  circle(30) above
  rectangle(6, 20) above
  rectangle(20, 6)
)

3.5.2 ぶれない標的

ターゲットを赤と白で色付けしてみよう。(もし前問で追加した場合は) スタンドは茶色、芝生は緑に色付けしてみよう。 例としては fig. 16 を参照してください。

Figure 16: 色付けしたアーチェリーのターゲット

Figure 16: 色付けしたアーチェリーのターゲット

ここでのコツはカッコをうまく使って合成の順序を制御することです。 fillColor()lineColor()、そして lineWidht() メソッドは単一のイメージに適用され、イメージが正しい形から構成されているかを確認する必要があります。

(
  ( circle(10) fillColor Color.red ) on
  ( circle(20) fillColor Color.white ) on
  ( circle(30) fillColor Color.red lineWidth 2 ) above
  ( rectangle(6, 20) above rectangle(20, 6) fillColor Color.brown ) above
  ( rectangle(80, 25) lineWidth 0 fillColor Color.green )
)

4 大きなプログラムの書き方

console にプログラムを打ち込んでいくのが辛くなってきています。 この章では、より大きなプログラムを書くためのツールを解説します:

例題を Doodle の sbt console 内で実行した場合は、何もしなくても動作するはずです。そうじゃない場合は、以下の import 文を使って Doodle を使用可能な状態にする必要があります。

import doodle.core._
import doodle.core.Image._
import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

4.1 console 内での作業

テキスト・エディタや IDE を使ってコードをファイルに保存できるようになりますが、Scala コンパイラが探せるように正しい場所に保存する必要があります。 Doodle テンプレートから作業している場合は、コードは src/main/scala/ ディレクトリに保存してください。

保存したコードを console から使うにはどうしたらいいでしょうか? console 内だけで動作する特別なコマンドがあって、それを使ってファイルに保存したコードを実行することができます。 このコマンドは :paste3 と呼ばれています。:paste に続けて実行したいファイル名を書きます。例えば、src/main/scala/Example.scala というファイルに式

circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed

を保存したとすると、以下のように console に書くことでこのコードを実行することができます。

:paste src/main/scala/Example.scala
// res0: doodle.core.Image = ContextTransform(<function1>,ContextTransform(<function1>,Circle(100.0)))

上の例では res0 という名前が与えられたことに注目してください。あなたが console に打ち込んで追従しているとしたら、今まで console に何を書いたのかによって別の数になったかもしれません。res0.draw (もしくは、あなたの console のための別の名前) を評価することでこのイメージを描画することができます。

4.1.1 console を使うためのコツ

console をより効率良く使うためのコツです:

コードをファイルに保存し始めると、次回 sbt を起動したときにコンパイラーが私たちのコードを見て怒るのに気付くかもしれません。その対策方法は次の節で解説するので続きを読んでください。

4.2 console 外でのコーディング

これまで console で書いてきたコードは、console 外で実行すると問題が発生します。例えば、以下のコードを src/main/scala 内の Example.scala に書いてください。

Image.circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed

次に、sbt を再起動して console に入ってみましょう。以下のようなエラーが表示されるはずです。

[error] src/main/scala/Example.scala:1: expected class or object definition
[error] circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed
[error] ^
[error] one error found

IDE を使っている場合も似たようなエラーが出てくるはずです。

問題は、このようになっています:

そのため、これらの制約を知って、ファイルに書くコードの書き方を変える必要があります。

エラーメッセージにヒントが隠されています。expected class or object definition (クラスまたはオブジェクトを期待する)。クラスが何かはまだ分かりませんが、オブジェクトのことは知っています – 全ての値はオブジェクトです。Scala では、全てのコードはオブジェクトかクラス内に書かれる必要があります。オブジェクトは、以下のように式をラッピングすることで定義できます。

object Example {
  (circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed).draw
}

次は別の理由でコンパイルが通りません。以下のようなエラーが沢山出てきたと思います。

[error] doodle/shared/src/main/scala/doodle/examples/Example.scala:2: not found: value circle
[error]   (circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed).draw
[error]    ^

私たちが circle という名前を使ったけども、この名前が何を指しているのか分からないと、コンパイラは言っています。 上のコード内の Color でも同様の問題が発生します。 名前に関する詳しい解説は後ほど行います。 とりあえず今の所は import 文を書いて、これらの名前の値をどこから見つければいいのかを教えてあげましょう。 Color という名前は doodle.core というパッケージの中にあり、circle という名前は doodle.core 内の Image オブジェクトの中にあります。 doodle.core 内の全ての名前と Image オブジェクト内の全ての名前を使うようにコンパイラに指示するには以下のように書きます。

import doodle.core._
import doodle.core.Image._

コードが完全に動作するためには、コンパイラは他にもいくつかの名前を探す必要があります。 それらは以下のように import します。

import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

これらの import 文はファイルの一番上に書くので、コード全体はこのようになります:

import doodle.core._
import doodle.core.Image._
import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

object Example {
  (circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed).draw
}

これで問題なくコードがコンパイルするはずです。

これで、sbt 内の console に行った場合は、私たちのコードをさっき名付けた Example という名前を使って参照することができます。

Example // draws the image

練習問題

まだ行っていなければ、上のコードを src/main/scala/Example.scala というファイルに保存して、コードがコンパイルされて console からアクセスできることを確認してみよう。

4.3 名前

前の節では多くの新しい概念を紹介しました。 この節では、そのうちの 1つ「値に名前をつけること」をもう少し掘り下げて見てみましょう。

私たちは、名前を使って色んな物を参照します。 例えば「プロフェスール・エミル・ペロ」(Professeur Emile Perrot) はとても香りの強いバラの品種を指し、「チェリー・パフェ」(Cherry Parfait) は非常に病気に強いけどもほとんど香りがしない品種のことです。 話し言葉においてこの関係が正確にはどのようになっているのかに関して、人々は多くの考察を与えてきました。 プログラミング言語はより制限されているため、正確な定義を与えることができます: 名前は値を指します。 名前が値に束縛されている、もしくは名前がバインディングを導入するといった言い方をすることもあります。 値が名前を持つ場合は、値をそのまま書いて使うことができる所全てで、代わりに名前を使うことができます。 別の言い方をすると、名前は値が参照する値に評価されます。 ここから必然的に出てくる疑問があります: どうやって値に名前を与えるのでしょう? Scala ではいくつの方法があるので、それを見ていきましょう。

4.3.1 オブジェクトリテラル

オブジェクトリテラルの宣言の仕方は既に見ています。

object Example {
  (circle(100) fillColor Color.paleGoldenrod lineColor Color.indianRed).draw
}

これは、今までに見てきた他のリテラル式同様にリテラル式ですが、この場合 Example という名前のオブジェクトを作ります。 プログラムの中で Example という名前を使うと、このオブジェクトに評価されます。

Example
// Example.type = Example$@76c39258

console 内で何回か試してみてください。 名前を使ったときの違いに気づいたでしょうか? Example という名前を最初に使ったときは絵が描かれたけども、次回以降は何も起こらなかったことに気づいたかもしれません。 オブジェクトの名前を初めて使ったときにはオブジェクトの本文が評価されて、オブジェクトが作られます。 次回以降の名前の使用時にはオブジェクトが既に存在するので再評価されません。 この場合は、オブジェクトの内部の式が draw メソッドを呼ぶため私たちはこの違いに気付くことができました。 もしこれを 1 + 1 (もしくは、draw を抜いただけのもの) などに置き換えると違いは分からなくなります。 これに関しては後ほどの章で存分に解説します。

このオブジェクトの型は何なのか気になるかもしれません。 console に聞いてみましょう。

:type Example
// Example.type

Example の型は Example.type で、他に値を持たない固有の型です。

4.3.2 val 宣言

オブジェクトリテラルはオブジェクトの作成と名前の定義を同時に行います。 この 2つを分けて、既に存在する値に名前を与えられると便利です。 val 宣言を用いてそれを行うことができます。

val を使うには

val <名前> = <値>

<名前><値> をそれぞれ名前と値に評価されるような式をそれぞれ書きます。

具体例で解説します。

val one = 1
val anImage = Image.circle(100).fillColor(Color.red)

これら 2つの宣言は oneanImage という名前を定義します。 後からこれらの名前をコードの中で使って値を参照することができます。

one
// res0: Int = 1

anImage
// res1: doodle.core.Image = ContextTransform(doodle.core.Image$$Lambda$8479/247257415@46e720c5,Circle(100.0))

4.3.3 宣言

上の節で、宣言と定義という言い方をしました。 これらの用語が正確には何を意味するのかを理解して、objectval の違いをより深く見ていきましょう。

式については分かっています。 それらは、値に評価されるプログラムの一部です。 宣言定義は、プログラムの別の部分で、それらは値に評価されません。 代わりに、それらは何かに名前を与えます。実は、Scala では値だけではなく型も宣言できますが、これに関してはここではあまり考察しません。 objectval は両方とも宣言です。

宣言と式が別になっていることの結果の 1つとして、以下のようなプログラム

val one = ( val aNumber = 1 )
// <console>:2: error: illegal start of simple expression
//        val one = ( val aNumber = 1 )
//                    ^

val aNumber = 1 が式ではなく、値に評価されないため書くことができません。

しかし、以下のようには書くことができます。

val aNumber = 1
// aNumber: Int = 1

val one = aNumber
// one: Int = 1

4.3.4 トップレベル

値に名前を与えるのに objectval 宣言という別々の方法があるのに納得がいかないかもしれません。 名前を宣言するのに val を使って、object は名前を付けずにオブジェクトの作成を行えばいいんじゃないでしょうか? 名前を付けずにオブジェクトリテラルを宣言することはできるでしょうか?

Scala はそれを許しません。 例えば、以下のようには書くことができません。

object {}
// <console>:2: error: identifier expected but '{' found.
//        object {}
//               ^

オブジェクトリテラルは必ず名前を付ける必要があります。

Scala は、トップレベルと呼ばれるコードとその他のものを区別します。 トップレベルにあるコードは、それをラッピングするコードを一切外側に持ちません。 別の言い方をすると、それは object に包むことなくファイルに直接書いてコンパイルすることができるものです。

式はトップレベルではないことを見ました。 val もトップレベルではありません。 しかし、オブジェクトリテラルはトップレベルです。

この区別は、ちょっと面倒なものです。 他の言語にはこの区別が無いものもあります。 Scala の場合は、Scala が Java コードを実行させるための Java Virtual Machine (JVM) 上に構築されていることによります。 Java がトップレベルとその他のコードを区別するために、Scala も JVM 上で動作するために仕方がなく区別をする必要があります。 Scala の console はこのトップレベル区別を行わないため、Scala を習い始めたときに混乱しやすいポイントとなります (console に書かれたもの全てがオブジェクトにラッピングされたいると思ってください)。

オブジェクトリテラルはトップレベルであることが許されているけども、val 宣言は許されていないということは、オブジェクトリテラル内に val を宣言できるということでしょうか? オブジェクトリテラル内に val を宣言した場合、後からその名前を参照することができるでしょうか?

できます!

このようにして、オブジェクトリテラル内に val を置くことができます:

object Example {
  val hi = "Hi!"
}

これを後から参照するには、既に使ってきた . 構文を使います。

Example.hi
// res2: String = Hi!

hi を単独で使うことはできないことに注意してください。

hi
// <console>:28: error: not found: value hi
//        hi
//        ^

Scala に対して、Example オブジェクト内で定義された名前 hi を参照したいと伝える必要があるからです。

4.3.5 スコープ

さっきの練習問題をやったとすると (やったよね?)、オブジェクト内で宣言された名前は、その名前を含んだオブジェクトも参照しないとオブジェクト外で使えないことを見ました。 具体例で解説すると、

object Example {
  val hi = "Hi!"
}
// defined object Example

以下のようには書くことができません。

hi
// <console>:28: error: not found: value hi
//        hi
//        ^

Scala に対して Example 内の中で hi を探す必要があると伝える必要があります。

Example.hi
// res5: String = Hi!

名前が修飾無しで使える所のことを名前が可視 (visible) 状態であるという言い方をして、名前が可視状態である所のことをそのスコープと言います。 そのため、この気取った新しい用語を使うと、「hiExample 外では不可視である」または「Example 外では hi はスコープに無い」と言えます。

名前のスコープはどうやったら分かるでしょうか? ルールは簡単で、名前は宣言された位置から始まり、直近の外側の中括弧 ({}) の終わりまで可視状態にあります。 上の例では、hiExample の中括弧に囲まれているので、そこで可視状態にあります。 他では見えません。

オブジェクトリテラルをオブジェクトリテラルの中で宣言することができ、より細かなスコープの区別することができます。 具体例で解説すると、

object Example1 {
  val hi = "Hi!"

  object Example2 {
    val hello = "Hello!"
  }
}

hiExample2 内でもスコープ内です (Example2hi の外側の中括弧内で定義されているため)。 しかし、hello のスコープは Example2 だけに限定されているため、hi のスコープよりも小さなものです。

もしスコープ内で既に宣言されている名前を再宣言するとどうなるでしょうか? これはシャドーイングと呼ばれています。 以下のコードでは、Example2 内の hiExample1 内の hi を覆い隠します。

object Example1 {
  val hi = "Hi!"

  object Example2 {
    val hi = "Hello!"
  }
}

Scala はこれを許容しますが、コードがすごく分かりづらくなるので一般的には悪い考えです。

オブジェクトリテラルを使わなくても新しいスコープを作ることができます。 Scala は、中括弧を置くことでほぼ全ての場所でスコープを作ることができます。

例えば以下のように書けます。

object Example {
  val good = "Good"

  // Create a new scope
  {
    val morning = good ++ " morning"
    val toYou = morning ++ " to you"
  }

  val day = good ++ " day, sir!"
}

morning (と toYou) は新しいスコープ内で宣言されています。(このスコープには名前が無いため) このスコープを外側から参照することはできないので、宣言されているスコープ外から morning を参照することは不可能です。 残りのプログラムに知られたくない秘密があるときはこの方法を使って隠すことができます。

このような Scala での入れ子のスコープの振る舞いはレキシカルスコープと呼ばれます。 全ての言語がレキシカルスコープを持つわけではありません。 例えば、Ruby や Python はレキシカルスコープを持たず、JavaScript はやっと最近になって導入されました。 筆者の意見では、レキシカルスコープを持たない言語を設計するのは、グアテマラ産激辛トウガラシを大量に食べた後で手を洗わずにトイレに行くぐらい馬鹿げたことだと思います。

練習問題

名前とスコープが理解できているかをテストするために、以下のそれぞれの場合で answer の値を求めてみましょう。

val a = 1
val b = 2
val answer = a + b

まずはシンプルな例から。answer1 + 2 なので 3 です。

object One {
  val a = 1

  object Two {
    val a = 3
    val b = 2
  }

  object Answer {
    val answer = a + Two.b
  }
}

これもシンプルな例です。answer1 + 2 なので 3 です。Two.aanswer が定義されている場所ではスコープ外です。

object One {
  val a = 5
  val b = 2

  object Answer {
    val a = 1
    val answer = a + b
  }
}

ここでは Answer.aOne.a を覆い隠すので、answer1 + 23 となります。

object One {
  val a = 1
  val b = a + 1
  val answer = a + b
}

これは完全に普通のコードです。b の宣言の右辺項にある式 a + 1 は普通の式であるので、answer は再び 3 となります。

object One {
  val a = 1

  object Two {
    val b = 2
  }

  val answer = a + b
}

banswer が宣言されている所では b はスコープ外であるので、このコードはコンパイルを通りません。

object One {
  val a = b - 1
  val b = a + 1

  val answer = a + b
}

引っ掛け問題です! このコードは動作しません。ここでは ab がそれぞれに対して定義されているため循環依存となり、解決されません。

4.4 抽象化

前の節では名前について色々習いました。 気取ったプログラマー用語を使うと、名前は式を抽象化すると言えます。 これは、名前の定義することの本質を一言で言い表しているけども、ちょっと用語を解読してみよう。

抽象化とは、要らない詳細を取り除くという意味です。 例えば、数は抽象化の 1つです。 「1」という数の純粋な概念は自然界には存在しません。 それはいつも 1つのリンゴや 1冊の Creative Scala の本といった具合に 1つの物です。 算数を行うときに、数という概念は何を数えているのかという要らない詳細を抽象化して数だけを操作することができます。

同様に、名前は式を代理します。 式は値をどのように構築するかを教えてくれます。 その値に名前があれば、その値がどのように構築されるかは知らなくてもよくなります。 式は任意の複雑さを持つことができますが、名前を使うことでどれだけ複雑なのかを気にする必要が無くなります。 これが、名前は式を抽象化すると言ったときの意味です。 いつでも式が出てきたら、それは同じ値を持つ名前に置き換えることができます。

抽象化はコードを読み書きしやすくします。 fig. 17 のように箱の列を作る具体例を用いて説明しましょう。

Figure 17: ロイヤルブルーで塗った 5つの箱

Figure 17: ロイヤルブルーで塗った 5つの箱

この絵を作る単一の式を書くことは可能です。

(
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue) beside
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue) beside
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue) beside
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue) beside
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue)
)

このコードの中に内在するシンプルなパターンがありますが、それが見づらくなっています。 初見で全ての長方形が同じものであることが分かるでしょうか? 基本となる箱のコードに名前を付けるという抽象化を行うことでコードの見通しが良くなります。

val box =
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue)

box beside box beside box beside box beside box

これでどうやって箱が作られているのかと、絵の中で箱が 5回繰り返されているのが分かりやすくなりました。

練習問題

再びアーチェリー

前の章のアーチェリーのターゲットに戻ってみてみましょう。 fig. 18 を見てください。

Figure 18: アーチェリーのターゲット

Figure 18: アーチェリーのターゲット

前回イメージを作ったときは値に名前をつける方法を知らなかったので、1つの大きい式を書きました。 今回は、イメージの部品にそれぞれ名前をつけて、イメージがどのように構築されているのか他の人が分かりやすいようにしてください。 どの部分に名前を付けるべきで、他の部分は名前をつけるに足らないという判断は自分のセンスで行う必要があります。

私たちは、以下のようにターゲット、スタンド、地面を分けて名前を付けました。 これで最終的なイメージがどう構築されているのかの見通しが良くなったと思います。 私たちの考えでは、これ以上細かく部品に名前をつけても役に立たないと思いました。

val coloredTarget =
  (
    Image.circle(10).fillColor(Color.red) on
      Image.circle(20).fillColor(Color.white) on
      Image.circle(30).fillColor(Color.red)
  )

val stand =
  Image.rectangle(6, 20) above Image.rectangle(20, 6).fillColor(Color.brown)

val ground =
  Image.rectangle(80, 25).lineWidth(0).fillColor(Color.green)

val image = coloredTarget above stand above ground

一丁先を行く

より実践的な名前の使い方として、fig. 19 のような町並みの 1シーンを作ってみよう。 イメージの部品に名前を付けることによってかなりの繰り返しを省けるはずです。

Figure 19: 町並みの風景

Figure 19: 町並みの風景

これが私たちの解答です。 見ての通り、風景を小さな部品に分けることで比較的小さなコードに収めることができました。

val roof = Image.triangle(50, 30) fillColor Color.brown

val frontDoor =
  (Image.rectangle(50, 15) fillColor Color.red) above (
    (Image.rectangle(10, 25) fillColor Color.black) on
    (Image.rectangle(50, 25) fillColor Color.red)
  )

val house = roof above frontDoor

val tree =
  (
    (Image.circle(25) fillColor Color.green) above
    (Image.rectangle(10, 20) fillColor Color.brown)
  )

val streetSegment =
  (
    (Image.rectangle(30, 3) fillColor Color.yellow) beside
    (Image.rectangle(15, 3) fillColor Color.black) above
    (Image.rectangle(45, 7) fillColor Color.black)
  )

val street = streetSegment beside streetSegment beside streetSegment

val houseAndGarden =
  (house beside tree) above street

val image = (
  houseAndGarden beside
  houseAndGarden beside
  houseAndGarden
) lineWidth 0

4.5 パッケージとインポート

私たちのコードをコンパイルできるように変えたとき多くのimport 文を追加する必要がありました。 この節ではその解説を行います。

ある名前が別の名前を覆い隠すことができることは前に見ました。 これはプログラムが大きくなると、1つのプログラムの別々の部分が同じ名前を別の用途に使おうとして問題を引き起こす可能性があります。 スコープを作って外からの名前を隠すことはできますが、トップレベルで定義された名前の対策をする必要があります。

同様の問題が自然言語でも発生します。 例えば、あなたの弟と友達の両方が「ズィギー」という名前だとすると、その名前を使った時どっちを指しているのかを補足する必要があります。 文脈によっては明らかかもしれませんが、友達の場合は「ズィギー S」、弟の場合は「ズィギー」と呼び分ける必要があるかもしれません。

Scala では、名前を整理するのにパッケージを用います。 パッケージはトップレベルで定義される名前のためのスコープを作ります。 同一のパッケージ内のトップレベルの名前は全て同じスコープ内で定義されます。 別のスコープ内にあるパッケージの名前を持ってくるにはインポートを行う必要があります。

パッケージを作るのは簡単で、以下のように

package <名前>

ファイルの一番上に書いて、<name> を自分のパッケージ名に置き換えます。

パッケージ内で定義された名前を使うには import 文を使って、パッケージ名を指定した後、全ての名前なら _、いくつかの名前だけならその名前を書きます。

具体例で解説します。

console 内ではパッケージを定義することはできません。 以下のコードを動作させるためには、example パッケージ内のコードをファイルに置いてコンパイルする必要があります。

まずは、パッケージ内でいくつかの名前を定義してみましょう。

package example

object One {
  val one = 1
}

object Two {
  val two = 2
}

object Three {
  val three = 3
}

次に、これらの名前をスコープ内に持ち込むにはインポートを行います。 1つの名前だけインポートすることができます。

import example.One

One.one

OneTwo の両方をインポートする場合。

import example.{One, Two}

One.one + Two.two

もしくは、example 内の全ての名前をインポートする場合。

import example._

One.one + Two.two + Three.three

Scala では、スコープを定義することができる色んなものをインポートすることができ、これにはオブジェクトを含みます。 以下のコードは one をスコープにインポートします。

import example.One._

one

4.5.1 パッケージの整理

パッケージはトップレベルの名前が衝突するのを防ぐけども、パッケージ名同士の衝突はどうしたらいいでしょうか? 一般的には、パッケージは階層的に整理することで衝突を回避します。 例えば、Doodle では core パッケージは doodle パッケージ内に定義されます。

import doodle.core._

上のような import 文を使うとき、これはパッケージ doodle 内のパッケージ core が欲しくて、core と呼ばれているかもしれない他のパッケージでは無いことを明示します。

5 置き換えモデルによる評価

私たちのプログラムが何を行っているのかを理解するためには Scala の式がどのように評価されるのかというメンタルモデルが必要となります。 これまでの所は、くだけたモデルで何とかやってきました。 この節では、置き換えモデルによる評価を理解してもう少し形式化されたモデルにしていきます。 プログラミングの多くのこと同様に、気取った用語を使っていますがシンプルな概念です。 この場合、多分高校の数学で習ったことのある置き換えの話で、同じ考え方を新しい文脈に持ってきたものです。

例題を Doodle の sbt console 内で実行した場合は、何もしなくても動作するはずです。そうじゃない場合は、以下の import 文を使って Doodle を使用可能な状態にする必要があります。

import doodle.core._
import doodle.core.Image._
import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

5.1 置き換え

置き換えは、式を見たら、それが評価される値で置き換えることができるというものです。具体例で説明すると、

1 + 1

を見たら、これは 2 で置き換えることができます。 そのため、

(1 + 1) + (1 + 1)

のような複合式が出てきたら 1 + 12 に置き換えて

2 + 2

となり、それは 4 に評価されます。

これは、高校の数学で式を簡易化するときに行ったのと同様の論理的思考です。 当然計算機科学ではこの過程を指す気取った用語があります。 置き換えの他に、これを式を簡約すると言ったり、等式推論 (equational reasoning) と言ったりします。

置き換えは私たちのプログラムを筋道立てて考える方法を与えてくれます。別の言い方とすると、「何が起こっているのかを正確に知ることができる」ということです。 今まで見てきた全ての式に置き換えを適用することができます。 ここではイメージよりも数や文字列を使った例題の方が分かりやすいので、前の章で見た例題に戻ります:

1 + ("Moonage daydream".indexOf("N"))

前の例は少し大ざっぱでしたが、ここではもう少し正確にコンピューターが何を行っているかのステップを解説しましょう。 コンピューターを真似ていると思ってください。

+ を含む式は 2つのサブ式である 1("Moonage daydream".indexOf("N")) から構成されます。 まず左か、右かのどちらを先に評価するかを決める必要があります。 ここでは、適当に右のサブ式を選ぶことにします (この選択に関してはまた後ほど)。

サブ式である ("Moonage daydream".indexOf("N")) もまた 2つのサブ式 "Moonage daydream""N" から構成されます。 リテラル式自身は値では無いのでこれらも評価する必要があることを思い出して、再び右側から評価するとします。

リテラルである "N" は、値の "N" へ評価されます。 混乱を避けるために、この説明文中だけの約束事として、値の方は |"N"| と書くことにしましょう。(この || を含む式をコピー&ペーストしても、コンパイルできませんので注意してください。) これで、最初のステップにより 1つの式をその値に置き換えることができます。

1 + ("Moonage daydream".indexOf(|"N"|))

次に、サブ式の左辺側を評価して、リテラル式 "Moonage daydream" をその値である |"Moonage daydream"| に置き換えることができます。 これで以下のようになります:

1 + (|"Moonage daydream"|.indexOf(|"N"|))

これで (|"Moonage daydream"|.indexOf(|"N"|)) という式全体を評価できるようになり、これは |-1| に評価されます (ここでも縦棒を使って整数値とリテラル式を区別しています)。 再び置き換えを使って以下を得ます:

1 + |-1|

次に左辺のリテラル 1 を評価して |1| を得ます。 置き換えを行って、以下を得ます:

|1| + |-1|

これで式全体を評価できるようになり、以下を得ます:

|0|

Scala に式全体を評価してもらって検算しましょう。

1 + ("Moonage daydream".indexOf("N"))
// res4: Int = 0

正解です!

ここまでを見て、いくつか気づいたことがあると思います:

たまたま Scala が行っている置き換え順を選ぶことができたのか (違いますが、まだこれは調査していません)、どの順で評価しても関係無いのでしょうか? 最初の足し算の例のように、正しい答えの得られる近道があるのはどの場合でしょうか? これらの質問を後ほど考察しますが、まずは名前がある場合に置き換えがどうなるかを見ていきましょう。

5.1.1 名前

名前の置き換えルールは、名前が参照する値で置き換えることです。 このルールは既にそれとなく使ってきたものですが、ここで形式化します。

具体例で解説すると、

val name = "Ada"
name ++ " " ++ "Lovelace"

このコードに置き換えを適用して

"Ada" ++ " " ++ "Lovelace"

を得ることができ、これは

"Ada Lovelace"

に評価されます。

これで置き換えプロセスの中で名前をより形式的に取り扱うことができるようになりました。 例えば、最初に例に戻ると

1 + 1

この式に名前を与えることができます:

val two = 1 + 1

以下のような複合式があるとき

(1 + 1) + (1 + 1)

置き換えによって 1 + 1two で置き換えて以下を得ることができます:

two + two

この式を計算したとき

1 + ("Moonage daydream".indexOf("N"))

私たちはサブ式に分解してから、それぞれを評価して置き換えました。 言葉を使ったため、これはかなり難解なものになってしまいました。 val 宣言を使うことで、これはよりコンパクトかる分かりやすく書き直すことができます。 以下は同じ式を部品に分解したものです。

val a = 1
val b = "Moonage daydream"
val c = "N"
val d = b.indexOf(c)
val e = a + d

ここで (現在では適当に) 上から下の順に評価が行われると定義した場合、異なる評価順を試して結果に影響が出るか実験することができます。

例えば、

val c = "N"
val b = "Moonage daydream"
val a = 1
val d = b.indexOf(c)
val e = a + d

は以前と同じ結果となります。 しかし、

val e = a + d
val a = 1
val b = "Moonage daydream"
val c = "N"
val d = b.indexOf(c)

ead に依存して、上から下の順序では ad が評価されていないためうまくいきません。 これは試すのも少し馬鹿げていると思うかもしれません。最終的に評価しようとしている式は e であり、ade のサブ式であるため、当然サブ式は式の前に評価される必要があります。

5.2 評価順序

評価順序の話をする準備が整いました。 評価順なんて関係あるのかと思うかもしれません。 これまで見た例だと、式をサブ式の前に評価してはいけないという問題以外では評価順は関係無いように見えます。

これらの問題の考察を行うには新しい概念を導入する必要があります。 ここまではほぼ全て純粋な式のみを取り扱ってきました。 これらは自由な順序で置き換えしても問題の無い式のことです4

非純粋な式は評価順に影響を受けるものです。 これまでに 1つ非純粋な式を見ていて、それは draw メソッドです。

Image.circle(100).draw
Image.rectangle(100, 50).draw

Image.rectangle(100, 50).draw
Image.circle(100).draw

を評価したとき、イメージを含むウィンドウが異なる順番で現れます。 特に面白みの無い違いですが、確かに違いではあります。

非純粋な式の特徴はそれらの評価が私たちに見える形の変化を引き起こすことです。 例えば、draw を評価するとイメージが表示されます。 これらの観測可能な変化を副作用もしくは作用と呼びます。 副作用を含むプログラムは、自由に置き換えを行うことができません。 しかし、副作用を使って評価順序を調査することができます。 それを行う道具は println メソッドです。

println メソッドはテキストを console に表示して (副作用)、Unit値に評価されます。 以下が具体例です:

println("Hello!")
// Hello!

println の console に表示するという副作用は評価順序を調べるのに便利なものです。 例えば、

println("A")
// A

println("B")
// B

println("C")
// C

を実行した結果は式が上から下へと評価することを示します。 println を使ってさらに調査してみましょう。

練習問題

println は置き換えができない

純粋なプログラムはどの式でも名前を与えてその式が出てきた所を名前で置き換えることができます。 具体例で示すと、

(2 + 2) + (2 + 2)

を置き換えて、以下のように書くことができ、

val a = (2 + 2)
a + a

プログラムの結果は変わりません。

非純粋な式の 1例として println を使って、この置き換えがうまくいかないこと、そのため副作用とも言われる非純粋な式が置き換えを壊すことを示してみよう。

以下はこれを示すシンプルな例です。 以下の 2つのプログラムが異なることを観測することができます。

println("Happy birthday to you!")
// Happy birthday to you!

println("Happy birthday to you!")
// Happy birthday to you!

println("Happy birthday to you!")
// Happy birthday to you!

val a = println("Happy birthday to you!")
// Happy birthday to you!
// a: Unit = ()

a

a

a

つまり、副作用があるときは自由に置き換えを使うことができないため、評価順序を気にする必要があると言えます。

狂気のメソッド

スコープを紹介したときにブロック式も見ましたが、そのときはその名前では呼びませんでした。 ブロックは中括弧 ({}) を使って作ることができます。それは中括弧内全ての式を評価します。ブロック内の最後の式の結果がブロック式の結果となります。

// 3 に評価される
{
  val one = 1
  val two = 2
  one + two
}
// res13: Int = 3

ブロック式を使って、何か役に立つ値に評価されるブロックの中に println を入れることで、メソッドのパラメータの評価順を調査することができます。

例えば、Image.rectangleColor.hsl とブロック式を使って Scala がメソッドパラメータを特定の順序で評価しているのか、そうだとしたらどの順序なのかを調べてみましょう。

セミコロン (;) で分けて書くことでブロックをよりコンパクトに 1行で書くことができることに注意してください。 これは普通はお行儀の良い方法ではありませんが、このような実験には役立つかもしれません。 以下が例となります。

// Evaluates to three
{ val one = 1; val two = 2; one + two }
// res15: Int = 3

以下のコードは、メソッドのパラメータが左から右へと評価されていることを示します。

Color.hsl(
  {
    println("a")
    0.degrees
  },
  {
    println("b")
    1.normalized
  },
  {
    println("c")
    1.normalized
  }
)
// a
// b
// c
// res16: doodle.core.Color = HSLA(Angle(0.0),Normalized(1.0),Normalized(1.0),Normalized(1.0))

これをよりコンパクトに書くとこうなります

Color.hsl({ println("a"); 0.degrees },
          { println("b"); 1.normalized },
          { println("c"); 1.normalized })
// a
// b
// c
// res17: doodle.core.Color = HSLA(Angle(0.0),Normalized(1.0),Normalized(1.0),Normalized(1.0))

ラストオーダー

Scala はどのような順序で式の評価を行っているでしょうか? 満足がいくまで必要な実験を行って答を探してみましょう。 Scala は、全ての式において一貫性のあるルールを適用していると仮定することができます。 異なる式に対する特別な場合はありません。

式は上から下へ評価され、メソッドのパラメータは左から右へと評価されることは既に見ました。 一般的な式が左から右へと評価されることをチェックしてみましょう。 これは以下のように比較的簡単に証明できます。

{ println("a"); 1 } + { println("b"); 2 } + { println("c"); 3}
// a
// b
// c
// res18: Int = 6

結果として、Scala の式は、上から下へ、左から右へと評価されていることが分かりました。

5.3 局所推論

副作用があるときには評価順序が大切であることを見てきました。 例えば以下のような副作用のある式があるとき、

disableWarheads() // 弾頭を無効化
launchTheMissles() // ミサイル発射

式が確かに上から下へと評価されて、ミサイルを発射する前に弾頭が無効化されていることを保証したいと思います。

作用はプログラムが世界に対して変化をもたらすことなので、全ての役に立つプログラムは何らかの作用を持ちます。 その作用はプログラムが終了した後に何らかの表示を行うことだけかもしれませんが、作用であることには違いありません。 副作用を最小限にするのは関数型プログラミングにおける重要なゴールの 1つなので、もう少しこの事に関してみてみましょう。

置き換えは非常に分かりやすいものです。 評価の順序が関係無ければ、今見ているコードの意味を他のコードが勝手に変えることが無いことを意味します。 1 + 1 は、他にどんなコードがプログラムに含まれていようとも 2 ですが、launchTheMissles() の作用は弾頭を既に無効化したかしないかに依存します。

結果として、純粋なコードは単独でも理解できることを意味します。 他のコードが意味を変えることが無いので、コードの一部だけを取り出して残りは無視することができます。 一方、非純粋なコードの意味はそれまで評価されて全てのコードに依存します。 この特性は局所推論 (local reasoning) と呼ばれます。 純粋なコードはこの特性を持ち、非純粋なコードはそれを持ちません。

プログラムが大きくなるにつれて全ての詳細を頭の中に入れておくのがどんどん辛くなっていきます。 私たちの頭の大きさは固定されている量なので、唯一の解法は抽象化を導入することです。 抽象化が無関係な詳細を取り除くということを覚えているでしょうか。 純粋なコードは残りのコードの全てが無関係な詳細であると言っているので究極の抽象化であると言えるでしょう。 この大きなプログラムを分かりやすくさせるという能力は、関数型プログラマーをワクワクさせる特性の 1つです。 関数型プログラムは作用を避けるという意味ではありません。全ての有用なプログラムは作用を持ちます。 関数型プログラムが目指すのは、作用をうまく制御することでコードの大部分をシンプルな置き換えモデルを使って推論できるようにすることです。

5.3.1 意味の意味

ここまでコードの意味について考察するとき、「意味」をコードが評価される結果もしくはそれが実行する副作用という意味で使ってきました。

置き換えでは、プログラムの意味はそれが評価されたものだと全く同じです。 そのため、同じ結果に評価される 2つのプログラムは等価です。 これは、副作用が置き換えを壊す理由です。置き換えモデルは副作用という考えを持たないので、作用に違いのある 2つのプログラムを見分けることができません。

プログラムは評価される結果以外でも異なることがあります。 例えば、同じ結果にたどり着くのに 1つのプログラムは別のものよりも長く時間がかかるかもしれません。 置き換えモデルはこれも区別しません。

置き換えは抽象化であり、値以外の全てのものを捨ててしまいます。 副作用、時間、メモリ使用量などは置き換えにとっては無関係なものですが、プログラムを書いたり実行したりする人にとってはそうではないかもしれません。 ここにトレードオフがあります。 より豊かなモデルを使ってこれらの詳細を捕捉することもできますが、取り扱いは難しくなります。 多くの人にとって多くの場合は、置き換えが非常にシンプルかつ役に立つものなので正しいトレードオフとなります。

6 メソッド

私たちは既にメソッドを使ってきました。メソッドを通じて私たちはオブジェクトと関わりを持つことができます。 この章では、独自のメソッドを書く方法を解説します。

名前は式を抽象化する方法を与えてくれます。 メソッドは式を抽象化して、一般化する方法を与えてくれます。 ここで一般化とは、関連する複数のもの (ここでは式) のグループを表現できる能力を指します。 メソッドは式のテンプレートを捉えて、その呼び手がメソッドのパラメータを渡すことでテンプレートの一部を補うことができます。

例題を Doodle の sbt console 内で実行した場合は、何もしなくても動作するはずです。そうじゃない場合は、以下の import 文を使って Doodle を使用可能な状態にする必要があります。

import doodle.core._
import doodle.core.Image._
import doodle.syntax._
import doodle.jvm.Java2DFrame._
import doodle.backend.StandardInterpreter._

6.1 メソッド

前に出てきた章の 1つの中で fig. 20 で示すイメージを以下のようなプログラムを使って作りました。

Figure 20: Five boxes filled with Royal Blue

Figure 20: Five boxes filled with Royal Blue

val box =
  Image.rectangle(40, 40).
    lineWidth(5.0).
    lineColor(Color.royalBlue.spin(30.degrees)).
    fillColor(Color.royalBlue)

box beside box beside box beside box beside box

ここで、箱の色を変えたいと思ったとします。 現状だと別の色のためにわざわざ式を書き直す必要があります。

val paleGoldenrod = {
  val box =
    Image.rectangle(40, 40).
      lineWidth(5.0).
      lineColor(Color.paleGoldenrod.spin(30.degrees)).
      fillColor(Color.paleGoldenrod)

  box beside box beside box beside box beside box
}

val lightSteelBlue = {
  val box =
    Image.rectangle(40, 40).
      lineWidth(5.0).
      lineColor(Color.lightSteelBlue.spin(30.degrees)).
      fillColor(Color.lightSteelBlue)

  box beside box beside box beside box beside box
}

val mistyRose = {
  val box =
    Image.rectangle(40, 40).
      lineWidth(5.0).
      lineColor(Color.mistyRose.spin(30.degrees)).
      fillColor(Color.mistyRose)

  box beside box beside box beside box beside box
}

これはつかれます。 それぞれの式は少ししか違いがありません。 大まかなパターンをとらえて、色違いだけを表すことができれば嬉しいです。 メソッドを宣言することでまさにそれを実現することができます。

def boxes(color: Color): Image = {
  val box =
    Image.rectangle(40, 40).
      lineWidth(5.0).
      lineColor(color.spin(30.degrees)).
      fillColor(color)

  box beside box beside box beside box beside box
}

// 色違いの箱を作る
boxes(Color.paleGoldenrod)
boxes(Color.lightSteelBlue)
boxes(Color.mistyRose)

自分で試してみて、全部書き下した場合とメソッドを使った場合で同じ結果を得られるかみてみましょう。

メソッドの宣言の例を 1つみたので、メソッドの構文の解説をする必要があります。続いて、メソッドの書き方、メソッド呼び出しのセマンティクス、どう置き換えするのかを見ていきましょう。

6.2 メソッド構文

私たちは既にメソッド宣言の 1例を見ています。

def boxes(color: Color): Image = {
  val box =
    Image.rectangle(40, 40).
      lineWidth(5.0).
      lineColor(color.spin(30.degrees)).
      fillColor(color)

  box beside box beside box beside box beside box
}

これをモデルとしてメソッド宣言の構文を理解していきましょう。 最初の部分はキーワード def です。 キーワードは Scala コンパイラにとって特別な意味を持つ単語で、この場合メソッドを宣言するという意味を持ちます。 これまでに objectval というキーワードも見てきました。

def の直後にはメソッドの名前が続き、この場合 boxes で、これは valobject がそれぞれ宣言するものの名前が直後に続くのに似ています。 val 宣言同様に、メソッド宣言はトップレベル宣言ではないので、ファイルに書く場合は object 宣言 (もしくはその他のトップレベル宣言) にラッピングされている必要があります。

次に、括弧 (()) で定義されるメソッドパラメータが続きます。 このメソッドパラメータは、呼び出す人がメソッドが評価する式に差し込むことができる部分です。 メソッドパラメータを宣言するとき、名前と型を与える必要があります。 コロン (:) を使って名前と型を分けます。 ここまでは型を宣言する必要はありませんでした。 ほとんどの場合、Scala は型推論という仕組みを使って型を自動的に計算してくれます。 しかし型推論はメソッドパラメータの型は推論できないため、私たちが与える必要があります。

メソッドパラメータの次に戻り値の型が来ます。 戻り型はメソッド呼ばれたときに評価される値の型です。 パラメータ型と違って Scala は戻り型を推論することができますが、自分で書くのが良い作法なので Creative Scala では戻り型を書くことにします。

最後に、メソッドが呼ばれたときに結果として返す値を計算するための式の本文が来ます。 本文は、boxes のようにブロック式のときもあれば、単一の式のときもあります。

メソッド宣言の構文

メソッド宣言の構文は

def methodName(param1: Param1Type, ...): ResultType =
  bodyExpression

  • methodName がメソッド名、
  • 省略可能な param1 : Param1Type, ... は 1つもしくはそれ以上のパラメータ名とパラメータ型の対、
  • 省略可能な ResultType はメソッドを呼んだ結果得られる値の型で、
  • bodyExpression はメソッドを呼んだ結果を計算するために評価される式。

練習問題

簡単な例題でメソッドの宣言を練習してみましょう。

2乗

Int の引数を受け取り、その引数の 2乗の Int を返す squire というメソッドを書いてみよう。(数の 2乗は自身を掛けることで得られるよ)

解答は

def square(x: Int): Int =
  x * x

手順を追って解にたどり着くことができます。

名前 (square) パラメータの型、そして戻り型 (Int) が与えられています。 ここから、以下のようなメソッドの骨組みを書くことができます。

def square(x: Int): Int =
  ???

パラメータの名前として x を選びました。 これは、適当な選択です。 特に意味のある名前が見つからないときは 1文字の xvi といった名前がよく出てきます。

ちなみに、これは既に妥当なコードです。 コンソールに入力してみてください。 このように宣言した場合、square を呼ぶとどんな結果となるでしょう?

次に、本文を完成させる必要があります。 2乗は数を自身で掛け算することだと言われているので、???x * x で置き換えます。 これは単一の式なので中括弧で囲む必要はありません。

ハーフ

Double の引数を受け取って、その引数を半分にした Double を返す halve というメソッドを書いてみよう。

def halve(x: Double): Double =
 x / 2.0

square で見たのと同じ手順でこの解が得られるはず。

6.3 メソッドのセマンティクス

メソッドの宣言の仕方が分かったので、セマンティクスを見ていきましょう。 置き換えモデルを使った場合、メソッド呼び出しはどう理解すればいいでしょう?

メソッド呼び出しはそれが評価する値へと置き換えることができると分かっています。 しかし、この値を導き出すためにはもう少しきめ細かいモデルを必要とします。 モデルを以下のように拡張します: メソッド呼び出しを見ると、新しいブロックを作り、そのブロック内ではパラメータをそれぞれに対応するメソッド呼び出し引数へと束縛してメソッド本体を置き換えます。

これで普通どおり置き換えを適用できるようになりました。

簡単な例を見てみましょう。以下のメソッドがあるとき

def square(x: Int): Int =
  x * x

このメソッド呼び出しは

square(2)

ブロックを導入して

{
  square(2)
}

パラメータ x2 に束縛して、

{
  val x = 2
  square(2)
}

メソッド本文を置き換えることで展開することができます。

{
  val x = 2
  x * x
}