実例によるPureScript

ウェブのための関数型プログラミング

Phil Freeman, "PureScript by Example - Functional Programming for the Web"

2 開発環境の準備

2.1 この章の目標

この章の目標は、作業用のPureScript開発環境を準備し、最初のPureScriptプログラムを書くことです。

これから書く最初のコードは、NPMとBowerから依存するライブラリを使用し、ビルド自動化ツールであるGruntを使用してビルドされるライブラリの例です。このライブラリは直角三角形の対角線の長さを計算する関数ひとつだけを提供します。

2.2 導入

PureScript開発環境を準備するために、次のツールを使います。

この章ではこれらのツールのインストール方法と設定を説明します。

2.3 PureScriptのインストール

PureScriptコンパイラをインストールするときに推奨される方法は、ソースからコンパイラをビルドすることです。PureScriptコンパイラはPureScriptのウェブサイトから64ビットのUbuntu用のバイナリディストリビューションとしてダウンロードすることもできますが、現在のところバイナリディストリビューションは主要なリリースについてだけ提供されています。もし最近のバグ修正や機能追加がなされた最新版に保ち、コンパイラで最新のパッケージをビルドできるようにしたいなら、最新のマイナーリリースをビルドするよう以下の指示に従ってください。

主なソフトウェア要件としては、Haskell Platformがインストールされていることです。お使いのオペレーティングシステムによっては、パッケージマネージャを使用してncurses開発パッケージもインストールする必要があるかもしれません(例えば、Ubuntuではlibncurses5-devパッケージとしての利用できます)。

Cabal実行ファイルの最新版を持っているのを確認することから始めましょう。

$ cabal install Cabal cabal-install

また、Cabalのパッケージ一覧が最新であることも確認してください。

$ cabal update

PureScriptコンパイラは、グローバルもしくはローカルディレクトリ内のCabalサンドボックス内のどちらかにインストールすることができます。この節ではグローバルにPureScriptをインストールし、その実行ファイルがパス上で利用できるようにする方法を説明します。

cabal installコマンドを使用して、HackageからPureScriptをインストールします。

$ cabal install purescript

これでコンパイラおよび関連する実行ファイルはあなたのパス上で利用できるようになるでしょう。確認のために、コマンドラインでPureScriptコンパイラを実行してみましょう:

$ psc

2.4 各ツールのインストール

もしNodeJSがインストールされていないなら、NodeJSをインストールする必要があります。そうするとシステムに npmパッケージマネージャもインストールされるはずです。 npmがインストールされ、パス上で利用可能であることを確認してください。

npm がインストールされたら、GruntとBowerもインストールする必要があります。プロジェクトがどこで作業しているかにかかわらずこれらのコマンドラインツールを利用可能にするため、通常はグローバルにインストールしておくのがいいでしょう。

$ npm install -g grunt-cli bower

これで、最初のPureScriptプロジェクトを作成するために必要なすべてのツールの用意ができたことになります。

2.5 Hello, PureScript!

まずはシンプルに始めましょう。PureScriptコンパイラ pscを直接使用して、基本的なHello World! プログラムをコンパイルします。3つの標準のコマンドですべての依存関係ライブラリを含めてゼロからアプリをビルドできるようになるまで、この章を読み進むにつれて開発手順をだんだんと自動化していきます。

まず最初に、ソースファイルのディレクトリ srcを作成し、src/Chapter2.pursという名前のファイルに以下のコードを貼り付けます。

module Chapter2 where

import Debug.Trace

main = trace "Hello, World!"

これは小さなサンプルコードですが、​​いくつかの重要な概念を示しています。

それではこのコードをビルドして実行してみましょう。次のコマンドを実行します。

$ psc src/Chapter2.purs

うまくいくと、大量のJavaScriptがコンソールに出力されるのを目にするはずです。コンソールに出力する代わりに、--outputコマンドラインオプションで出力をファイルにリダイレクトしてみましょう。

$ psc src/Chapter2.purs --output dist/Main.js

これでNodeJSを使用してコードを実行することができるはずです。

$ node dist/Main.js

うまくいくと、NodeJSはこのコードを正常に実行し、コンソールには何も出力されないはずです。これは、メインとなるモジュールの名前をPureScriptコンパイラに教えていないためです!

$ psc src/Chapter2.purs --output dist/Main.js --main=Chapter2

再びNodeJSで実行すると、今度は "Hello, World!" という単語がコンソールに出力されるのがわかるはずです。

2.6 使用されていないコードを取り除く

テキストエディタで dist/Main.jsファイルを開くと、大量のJavaScriptコードが書かれているのがわかります。これはコンパイラがPreludeと呼ばれるモジュール群で定義されている標準関数を追加しているためです。Preludeにはコンソールに出力するのに使う Debug.Traceモジュールが含まれています。

ここで生成されたコードのほとんどは実際には使用されていないので、別のコンパイラオプションを指定すると未使用のコードを削除することができます。

$ psc src/Chapter2.purs --output dist/Main.js --main=Chapter2 --module Chapter2

Chapter2モジュールで定義されたコードで必要とされているJavaScriptだけを含めるようpscに指示する--module Chapter2オプションを追加しました。生成されたコードをテキストエディタで開くと、次のように出力されているのがわかるはずです。

var PS = PS || {};
PS.Debug_Trace = (function () {
    "use strict";
    function trace(s) { 
      return function() {
        console.log(s);
        return {};  
      };
    };
    return {
        trace: trace
    };
})();

var PS = PS || {};
PS.Chapter2 = (function () {
    "use strict";
    var Debug_Trace = PS.Debug_Trace;
    var main = Debug_Trace.trace("Hello, World!");
    return {
        main: main
    };
})();

PS.Chapter2.main();

NodeJSを使用してこのコードを実行すると、先ほどと同じ文字列がコンソールに出力されるはずです。

ここでPureScriptコンパイラがJavascriptコードを生成する方法の要点が示されています。

PureScriptはシンプルで理解しやすいコードを生成すること重視しているので、これらの点は大切です。実際に、ほとんどのコード生成処理はごく軽い変換です。PureScriptについての理解が比較的浅くても、ある入力からどのようなJavaScriptコードが生成されるかを予測することは難しくありません。

2.7 Gruntによるビルドの自動化

今度は、PureScriptコンパイラオプションを毎回手で入力する代わりに、コードを自動でビルドできるように、Gruntを設定してみましょう。

プロジェクトディレクトリにGruntfile.jsという名前のファイルを作成し、次のコードを貼り付けてください。

module.exports = function(grunt) {

  "use strict";

  grunt.initConfig({

    srcFiles: ["src/**/*.purs"],

    psc: {
      options: {
        main: "Chapter2",
        modules: ["Chapter2"]
      },
      all: {
        src: ["<%=srcFiles%>"],
        dest: "dist/Main.js"
      }
    }
  });

  grunt.loadNpmTasks("grunt-purescript");
  
  grunt.registerTask("default", ["psc:all"]);
};

このファイルではNodeモジュールを定義しており、ビルド構成を定義するためにgruntモジュールをライブラリとして使用しています。JSONプロパティとしてコマンドラインオプションを指定してPureScriptコンパイラを呼び出せるgrunt-purescriptプラグインを使用しています。

grunt-purescriptプラグインは他にも便利な機能を提供しており、コードから自動的にMarkdownドキュメントを生成する機能や、ライブラリからpsci対話式コンパイラ向けの設定ファイルを自動生成する機能があります。興味があれば grunt-purescript プロジェクトのホームページを参照してみてください。

次のように入力して、ローカルのmodulesディレクトリに gruntライブラリとgrunt-purescriptプラグインをインストールしてください。

$ npm install grunt grunt-purescript@0.6.0

保存されたGruntfile.jsファイルを使うと、次のようにコードをコンパイルできるようになります。

$ grunt
>> Created file dist/Main.js.

Done, without errors.

2.8 NPMパッケージの作成

Gruntを設定したので、コンパイルするときに毎回コマンドラインにコマンドを入力する必要はなくなりましたが、もっと重要なのは、アプリケーションのエンドユーザはどちらも必要ないということです。そのためには、ビルドする前にNPMパッケージの必要なモジュールを自動的にインストールしておくという手順を追加しておきましょう。

依存関係が指定された独自のNPMパッケージを定義します。

プロジェクトディレクトリで initサブコマンドを指定してnpmを実行し、新しいプロジェクトを初期化します。

$ npm init

いろいろと質問されますが、それが終わるとpackage.jsonという名前のファイルがプロジェクトディレクトリに追加されます。このファイルではプロジェクトのプロパティを指定したり、依存するライブラリの指定を追加することができます。テキストエディタでこのファイルを開き、JSONオブジェクトに次のプロパティを追加しましょう。

"dependencies": {
  "grunt-purescript": "0.6.0"
}

このコードではインストールするgrunt-purescriptプラグインの厳密なバージョンを指定しています。

依存するライブラリを手作業でインストールするかわりに、エンドユーザーは単に npmコマンドを使用するだけで必要なものすべてをインストールできるようになりました。

$ npm install

2.9 Bowerによる依存関係の追跡

この章の目的となっているdiagonal関数を書くためには、平方根を計算できるようにする必要があります。purescript-mathパッケージにはJavaScriptのMathオブジェクトのプロパティとして定義されている関数の型定義が含まれていますので、purescript-mathパッケージをインストールしてみましょう。 npmの依存関係でやったのと同じように、次のようにコマンドラインに入力すると直接このパッケージをダウンロードできます。

$ bower install purescript-math#0.1.0

このコマンドは purescript-mathライブラリのバージョン0.1.0をそれが依存するライブラリと一緒にインストールします。

しかし、package.jsonを作成してNPMの依存関係を制御するためにnpm initを使用したのと同じような方法で、Bowerの依存関係が含まれているbower.jsonファイルを設定することができます。

コマンドラインに次のコマンドを入力します。

$ bower init

NPMの場合とちょうど同じように、いくつか質問をされ、それが終わると bower.jsonファイルがプロジェクトディレクトリに配置されます。この処理の途中で、すでに存在するライブラリの依存関係をプロジェクトファイルに含めたいかどうかを尋ねられるでしょう。「はい」を選択した場合は、bower.json にこのようなセクションがあるのがわかるでしょう。

"dependencies": {
  "purescript-math": "0.1.0"
}

エンドユーザーが手作業で依存するライブラリを指示する必要がなくなり、代わりに次のようにコマンドを呼び出すだけで依存するライブラリを取り込むことができるようになりました。

$ bower update

それでは、Bowerから取り込んだ依存先ライブラリをコンパイルに含めるように、Gruntスクリプトを更新してみましょう。Gruntfile.jsを編集し、ソースファイルについての行を次のように変更します。

srcFiles: ["src/**/*.purs", "bower_components/**/src/**/*.purs"]

この行では bower_componentsディレクトリのソースファイルをコンパイルするソースファイルに含めています。独自のBower構成がある場合は、それに応じてこの行を修正する必要があるかもしれません。

なぜNPMとBowerの両方を使うのか?

疑問に思ったかもしれませんが、なぜ2つ​​のパッケージマネージャを使い分ける必要があるのでしょうか?PureScriptライブラリをNPMレジストリに含めることはできないのでしょうか?

PureScriptコミュニティは、さまざまな理由でPureScriptの依存ライブラリをBowerを使用して標準化しています。

  • PureScriptのライブラリパッケージがJavaScriptのソースコードを含むことはめったになく、コンパイルされないままでNPMレジストリへ配置するのには適していません。
  • Bowerレジストリは、直接コードをホスティングする代わりに、既存のGitリポジトリのパッケージ名とバージョンの対応関係だけを管理しています。これによりコミュニティがコードおよびリリースを管理するのにGitHubのような既存のツールを使用することができます。
  • BowerはCommonJSのモジュール標準のような特定の配置に従うようパッケージに要求してしません。

もちろん、任意のパッケージマネージャを自由に選択して使用することもできます。PureScriptコンパイラおよびツール群は、Bowerに(またはNPM、Gruntなどにも)依存しているわけではありません。

2.10 対角線の長さの計算

それでは外部ライブラリの関数を使用する例として diagonal関数を書いてみましょう。

まず、 src/Chapter2.pursファイルの先頭に次の行を追加し、Mathモジュールをインポートします。

import Math

そして、次のようにdiagonal関数を定義します。

diagonal w h = sqrt (w * w + h * h)

この関数の型を定義する必要はないことに注意してください。diagonal は2つの数値を取り数を返す関数である とコンパイラは推論することができます。しかし、ドキュメントとしても役立つので、通常は型注釈を提供しておくことをお勧めします。

それでは、新しいdiagonal関数を使うようにmain関数も変更してみましょう。

main = print (diagonal 3 4)

Gruntを使用して、モジュールを再コンパイルします。

$ grunt

生成されたコードを再び実行すると、このコードが正常に呼び出されたことがわかるでしょう。

$ node dist/Main.js 

5

2.11 対話式処理系を使用したコードのテスト

PureScriptコンパイラには psciと呼ばれる対話式のREPL(Read-eval-print loop)が付属しています。psciはコードをテストしたり思いついたことを試すのにとても便利です。それでは、psciを使ってdiagonal関数をテストしてみましょう。

grunt-purescriptプラグインは、ソースファイルに応じてpsci設定を自動で生成するように設定することができます。これによりpsciに手作業でモジュールを読み込む手間を省くことができます。

これを設定するには、Gruntfile.jsファイルに以下のようなpscpscMakeという新しいビルドターゲットを追加します。

dotPsci: ["<%=srcFiles%>"]

また、デフォルトのタスクにこのターゲットを追加しておきましょう。

grunt.registerTask("default", ["psc:all", "dotPsci"]);

これで gruntを実行すると.psciファイルがプロジェクトディレクトリに自動生成されるようになりました。このファイルは、 psciの起動時に設定で使用されるコマンドを指定するのに使われます。

それでは psciを起動してみます。

$ psci
> 

コマンドの一覧を見るには、:?と入力します。

> :?
The following commands are available:

    :?              Show this help menu
    :i <module>     Import <module> for use in PSCI
    :m <file>       Load <file> for importing
    :q              Quit PSCi
    :r              Reset
    :t <expr>       Show the type of <expr>

Tabキーを押すと、自分のコードで利用可能なすべての関数、及びBowerの依存関係とプレリュードモジュールのリストをすべて見ることができるはずです。

幾つか数式を評価してみてください。psciで評価を行うには、1行以上の式を入力し、Ctrl+ Dで入力を終了します。

> 1 + 2
3

> "Hello, " ++ "World!"
"Hello, World!"

それではpscidiagonal関数を試してみましょう。

> Chapter2.diagonal 5 12

13

また、psciで関数を定義する使こともできます。

> let double x = x * 2

> double 10
20

コード例の構文がまだよくわからなくても心配はいりません。 この本を読み進めるうちにわかるようになっていきます。

最後に、:tコマンドを使うと式の型を確認することができます。

> :t true
Prim.Boolean

> :t [1, 2, 3]
[Prim.Number]

psciで試してみてください。もしどこかでつまづいた場合は、メモリ内にあるコンパイル済みのすべてのモジュールをアンロードするリセットコマンド:rを使用してみてください。

2.12 任意: CommonJSのモジュールのビルド

PureScriptコンパイラのpscコマンドは、ウェブブラウザでの使用に適した、単一の出力ファイルにJavaScriptコードを生成します。それとは別に、コンパイルにはpsc-makeという選択肢もあります。psc-makeでは、コンパイルされるPureScriptモジュールそれぞれについて、個別のCommonJSモジュール生成することができます。もしCommonJSモジュール標準に対応したNodeJSのような実行環境を対象とするなら、psc-makeのほうが望ましい場合があるでしょう。

コマンドラインでpsc-makeを実行するには、入力ファイルを指定し、--outputオプションでCommonJSモジュールが作成されるディレクトリも指定します。

$ psc-make src/Chapter2.purs --output dist/

与えられた入力ファイルのそれぞれのモジュールについて、dist/ディレクトリの中にサブディレクトリが作成されるでしょう。Bowerの依存関係を使用している場合は、bower_components/ディレクトリ内のソースファイルを含めることを忘れないでください!

grunt-purescriptプラグインはpsc-makeを使用したコンパイルにも対応しています。Gruntから psc-makeを使用するには、Gruntfile.jsファイルを次のように変更します。

これで、 Chapter2モジュールとその依存先ライブラリそれぞれについて、gruntコマンドラインツールが dist/の下にサブディレクトリを作成するようになりました。

2.13 Gruntプロジェクトテンプレートの使用

様々なビルドプロセスに対応するために、NPMやGrunt、Bowerはいろいろな方法でカスタマイズすることができます。しかし、簡単なプロジェクトでは、この手順はGruntプロジェクトテンプレートを使用して自動化することもできます。

grunt-initツールは、テンプレートを利用して簡単なプロジェクトを開始する方法を提供します。grunt-init-purescriptプロジェクトは、簡単なテストスイートを含むPureScriptプロジェクトのシンプルなテンプレートを提供します。

grunt-initを使用してプロジェクトを設定するには、最初にNPMを使用してgrunt-initのコマンドラインツールをインストールします。

$ npm install -g grunt-init

そして、ホームディレクトリにPureScriptテンプレートを複製してください。たとえば、LinuxやMacでは、次のようにします。

$ mkdir ~/.grunt-init
$ git clone https://github.com/purescript-contrib/grunt-init-purescript.git \
    ~/.grunt-init/purescript

これで、新しいディレクトリに簡単なプロジェクトを作成できるようになりました。

$ mkdir new-project
$ cd new-project/
$ grunt-init purescript

いくつかの簡単な質問を受けたあと、現在のディレクトリにプロジェクトが初期化されますので、これまで見てきたコマンドを使ってビルドの準備をしましょう。

$ npm install
$ bower update
$ grunt

最後のコマンドでは、ソースファイルをビルドし、テストスイートを実行しています。

より複雑なプロジェクトの雛形として、このプロジェクトテンプレートを使用することができます。

演習

  1. (簡単) Math.pi定数を使用し、指定された半径の円の面積を計算する関数 circleAreaを書いてみましょう。また、psciを使用してその関数をテストしてください。

  2. (やや難しい) node dist/Main.jsと入力する代わりにユーザが単に grunt runを入力するだけで、コンパイルされたコードをNodeJSで実行できるように、Gruntfile.jsファイルにタスクを追加しましょう。ヒントgrunt-execute Gruntプラグインを使用することを検討してください。

2.14 まとめ

この章では、JavaScriptのエコシステムのNPMとBower、Gruntという標準的なツールを使用し、一から開発環境をセットアップしました。

また最初のPureScript関数を書き、コンパイルし、NodeJSを使用して実行することができました。

以降の章では、コードをコンパイルやデバッグ、テストするためにこの開発設定を使用しますので、これらのツールや使用手順に十分習熟しておくとよいでしょう。