純粋関数型スクリプト言語PureScriptのはじめかた

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">&quot;PureScript is more like Haskell than Haskell itself.&quot; – `<a href="https://twitter.com/thebeardedzug">@thebeardedzug</a></p>&mdash; Rahul Goma Phulore (@missingfaktor) <a href="https://twitter.com/missingfaktor/status/845400837841195009">2017年3月24日</a></blockquote>

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# はじめに

スクリプト言語**[PureScript](http://www.purescript.org/)**の開発環境構築から簡単なアプリケーションの作成までの手順を紹介します。この記事では、基本的なコマンドライン環境のアプリケーションだけでなく、Halogenというユーザインターフェイスのフレームワークや、簡単なサーバサイドアプリケーションを動かすところまでを紹介しています。そのため、シングルページアプリケーションのような実用性の高いアプリケーションを作って試せるようになっています。ブラウザ環境で動けば、ウェブページに組み込んで自分が作ったアプリケーションを簡単に他の人に見せられますし、[Electron](http://electron.atom.io/)を使って単独で動くウィンドウアプリケーションなんかも作ることができるなど、夢が広がりますね。




## PureScriptとはどんな言語？

PureScriptにはいろいろな側面がありますが、まずは**AltJS**、つまりJavaScriptを主なコンパイルターゲットとする言語であるのが大きな特徴でしょう。JavaScriptが動くあらゆる環境、つまりブラウザやNodeでの実行が主眼に置かれており、またJavaScriptでは難しかった大規模な開発に耐えうる極めて高い堅牢性や可読性を備えています。膨大なJavaScriptのライブラリ資産をPureScriptから活用することも比較的簡単です。なお、C++やErlangのバックエンドの開発も行われています。

他にも、**関数型プログラミング言語**としての側面も持ちます。PureScriptの言語仕様やコンセプトの大半は、関数型プログラミング言語の代表的存在のひとつであるHaskellがもとになっています。しかしPureScriptはそのHaskellのコンセプトを更に洗練し、長い歴史の中で見えてきたHaskellの数々の問題点をことごとく修正し、まったくの新規の言語としてすべてを作り直しているようなものです。そのため、**PureScriptはHaskell自身よりもHaskellらしい**とすら言われることもあります。また、Elmのようにシンプルさを追求した言語ではなく、関数型言語としての高度な機能が存分に盛り込まれたパワフルな言語です。

そして忘れてはいけないのは、PureScriptは**スクリプト言語**であることです。言語仕様はまったくスクリプト言語っぽくないですが、名前に『スクリプト』って入っているのでたぶんスクリプト言語なのでしょう。『純粋関数型言語』だからと肩肘張らず、スクリプト言語らしい、ゆるい気持ちで取り組みましょう。


## 前提となる知識

PureScriptは主にAltJSとしてJavaScript環境で開発される以上、JavaScriptの周辺ツールを頻繁に使うことになります。以下のような事項については、概要だけでもいいので頭に入れておきましょう。

* HTML/CSS/JavaScriptを使った基本的なウェブサイトの構築
* Node環境でのアプリケーション開発。CommonJSモジュール
* npmによるパッケージ管理
* git

gitやnode、npmは予めインストールしておきましょう。ここに挙げられているものでよく知らないものがあれば、先に簡単に調べておきましょう。この記事ではgitは直接は扱いませんが、PureScriptのパッケージマネージャSpagoはgitに依存しているためgitのインストールも必要です。





# Node環境での開発

まずは一番シンプルに、Nodeのコマンドライン環境でPureScriptコードを動かすところまで説明します。

## コンパイラとパッケージマネージャのインストール

nodeがインストールされていることを確認したら、まずは次のコマンドで、最新版のPureScriptコンパイラと、パッケージマネージャ兼ビルドツールである**[spago](https://github.com/spacchetti/spago)**をインストールしてください[^local][^bower][^pulp]。

```shell-session
$ npm install --global purescript spago
```

もしかしたら、環境によっては次のようなエラーメッセージでうまくいかないかもしれません。

```console
error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
```

PureScriptコンパイラは`ncurses`というライブラリに依存しているそうで、次のようにして`libncurses5`をインストールすればいいかもしれません。

```console
$ sudo apt update
$ sudo apt install libncurses5
```

[^local]: ここでは`--global`オプションをつけてグローバルにインストールしていますが、もちろんお好みでローカルにインストールしても構いません。

[^bower]: PureScriptでは以前はパッケージマネージャとしてBowerが使われており、またpsc-packageというツールの開発も試験的に行われていましたが、Spagoが登場したことにより、今後はBowerからSpagoへと移行していくのではないかと思われます。現状でもBowerを使うのが前提になっているライブラリも多いですが、Spagoはそうしたライブラリでもそのまま問題なく扱えるので、この記事ではSpagoを使って説明していくことにしました。なお、Bowerは非推奨のツールになったというだけで、Bowerではパッケージ管理に何か致命的な不具合があるということではなく、Bowerも使おうと思えば使うことができます。

[^pulp]: また、以前この記事ではpulpというビルドツールを扱っていましたが、Spagoがパッケージマネージャとビルドツールを兼ねたツールなので、pulpについての説明はこの記事から省きました。pulpが使えなくなったわけではありませんので、spagoで何か都合が悪いようであればpulpも試してみてください。また、pursuitでパッケージを公開するにはpulpが必要だそうなので、pulpが不要になったわけではないですし、完全にbowerから足を洗うことができたわけでもないようです。







## プロジェクトの作成 

次はプロジェクトを作成しましょう。どこかに空のディレクトリを作成してそこに移動し、`spago init`コマンドを実行します。ここでは、`hello`ディレクトリを作って、その中で`spago init`を実行しています。

```shell-session
$ mkdir hello
$ cd hello
$ spago init
```

成功すると、次のような構成のサンプルプロジェクトが作成されると思います。

```
.
├── packages.dhall
├── spago.dhall
├── src
│   └── Main.purs
└── test
    └── Main.purs
```


`src`がPureScriptソースコードを保存するディレクトリです。また、`.purs`という拡張子のファイルがPureScriptソースファイルの拡張子です。 `src/Main.purs`というファイルを開いてみると、次のようなコードが書かれているのがわかるかと思います[^justin]。

```haskell:src/Main.purs
module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)

main :: Effect Unit
main = do
  log "🍝"
```



[^justin]: 出力されるのはなぜかスパゲッティの絵文字です。Spagoのプロジェクトを開始した[ジャスティンさん](https://twitter.com/jusrin00)は、ツールやライブラリに食べ物の名前を付けるのが好きみたいです。ツイッターにもよく食べ物の画像を流しています。

## ビルドと実行

それでは、このサンプルプロジェクトを動かしてみましょう。アプリケーションを起動するには**`spago run`コマンド**を実行します。このとき、自動的に再ビルドも行われます。

```shell-session
$ spago run
Installing 4 dependencies.
Searching for packages cache metadata..
Recent packages cache metadata found, using it..
Copying from global cache: "effect"
Copying from global cache: "prelude"
Copying from global cache: "console"
Copying from global cache: "psci-support"
Installation complete.
Compiling Type.Data.Row
Compiling Type.Data.RowList
Compiling Record.Unsafe
Compiling Data.Symbol

...

Compiling Main
Compiling PSCI.Support
Compiling Effect.Class.Console
Compiling Test.Main
Build succeeded.
🍝
```

こんな感じで、コンパイルされたモジュールの名前がだらーっと出力されたあと、コンパイル全体が成功すると`Build successful`と出力されます。そして、コンパイルされた結果のJavaScriptコードが`output`ディレクトリ以下に出力されているはずです。それからアプリケーションが起動して、`🍝`が出力されたことがわかります[^spaghetti]。

[^spaghetti]: なお、私の環境では絵文字がちゃんと表示されず、`�`になってしまいました。かなしい。

なお、プログラムの実行はせずにビルドだけを行うには、**`spago build`コマンド**を使います。また、テストを実施するには**`spago test`コマンド**を使います。`spago test`コマンドを実行すると、`src/Main.purs`にある`Main`モジュールではなく、`test/Main.purs`ファイルの`Test.Main`モジュールがコンパイルされて実行されます。

## パッケージの追加

PureScriptコンパイラに付属するモジュールは、最低限のデータ型を定義するモジュールである[Prim](https://pursuit.purescript.org/builtins/docs/Prim)と、型レベル計算の基礎となる型クラスである`Prim.Boolean`などのモジュールのみです。他には一切のライブラリが含まれず、言語組み込みのどんな関数も演算子も存在しないため、コンパイラのみでは標準出力はおろか、足し算すらできません。さきほどはspagoによってサンプルプロジェクトに必要なパッケージがもとから記述されていたためコンパイルが成功しましたが、今後開発が進むと必要なパッケージを手動でインストールすることになるでしょう。その場合も、spagoを使ってPureScriptパッケージをインストールします。

spagoでパッケージを追加するには、`spago install`コマンドを実行します。たとえば、`purescript-halogen`というライブラリをインストールしたければ、`spago install halogen`というコマンドを実行します。PureScriptのライブラリにはたいてい `purescript-` というプリフィックスが付けられていますが、`spago install`するときには`purescript-`は不要です。`spago.dhall`はこのプロジェクトの設定を記述するファイルですが、これを開いてみると次のように書いてあると思います。

```dhall:spago.dhall
{-
Welcome to a Spago project!
You can edit this file as you like.
-}
{ name =
    "my-project"
, dependencies =
    [ "effect", "console", "psci-support" ]
, packages =
    ./packages.dhall
, sources =
    [ "src/**/*.purs", "test/**/*.purs" ]
}

```

`spago init`で作成したプロジェクトは、`effect`, `console`, `psci-support`という3つのパッケージに依存していることがなんとなく読み取れるでしょう。それでは`spago install`を実行してみましょう。


```shell-session
$ spago install halogen
Installing 71 dependencies.
Searching for packages cache metadata..
Recent packages cache metadata found, using it..
Copying from global cache: "coroutines"
Copying from global cache: "bifunctors"
Copying from global cache: "catenable-lists"

...

Copying from global cache: "web-storage"
Copying from global cache: "web-touchevents"
Copying from global cache: "web-uievents"
Installation complete.
```

インストールが完了すると、次のようにhalogenへの依存関係が追加されているのがわかるでしょう。

```
, dependencies =
    [ "console", "effect", "halogen", "psci-support" ]
```



開発が進むと他にもいろいろなパッケージが欲しくなると思いますが、PureScriptのパッケージを探すには、[pursuit](https://pursuit.purescript.org/)というドキュメント検索サービスを使うのがいいでしょう。pursuitに登録されていないライブラリも結構あるので、[githubで探してみる](https://github.com/search?utf8=%E2%9C%93&q=purescript)のもいいと思います。

なお、`spago install`で直接インストールできるパッケージは、[package-sets](https://github.com/purescript/package-sets)に含まれているパッケージだけです。大抵はそれだけで問題ないと思いますが、package-setsに含まれないようなパッケージをインストールするには別の設定が必要になります。この記事ではそのあたりの説明は省略しますので、詳しくはspagoのドキュメントを参照してみてください。`spago install`でインストールできるパッケージの一覧は、`spago list-packages`コマンドで見ることができます。










# httpureによるサーバーサイドアプリケーションの開発

次は、[**httpure**](https://github.com/cprussin/purescript-httpure)というパッケージを使って、アクセスすると`hello world!`とだけ返す簡単なサーバサイドウェブアプリケーションを作ってみます。

さきほど作ったひな形のプロジェクトの`src/Main.purs`を編集して、次のようなコードを記述します。

```haskell:src/Main.purs
module Main where

import Prelude (($))

import Effect.Console as Console
import HTTPure as HTTPure

main :: HTTPure.ServerM
main =
  HTTPure.serve 8080 router $ Console.log "Server now up on port 8080"
  where
    router _ = HTTPure.ok "hello world!"
```

このコードは、Webサーバを起動し、リクエストがあったら`hello world!`とレスポンスを返すだけのプログラムです。`HTTPure.serve`でサーバを作成し、リクエストの読み取りを開始しています。そしてリクエストが来たら`HTTPure.ok "hello world!"`の部分が評価されて、ステータスコード200(OK)で`hello world!`というレスポンスが返却されます。

次に、必要なパッケージをインストールしましょう。先ほど述べたとおり`spago init`でインストールされるパッケージは最低限ですので、`purescript-httpure`パッケージを追加でインストールしましょう。それから`spago run`でアプリケーションを起動してみましょう。

```shell-session
$ spago install httpure

...

$spago run
[info] Build succeeded.
Server now up on port 8080
```

`[info] Build successful`と出力されたあと、最後の行で`localhost:8080`でListenしていることがわかります。これで、あとはブラウザで http://localhost:8080/ にアクセスすれば`hello world!`と表示されると思います。



# ブラウザ環境での実行

次は、ブラウザ環境でPureScriptを実行してみましょう。文字の出力くらいの簡単なコードなら、Node環境で実行したのと同じコードをブラウザ環境でもそのまま実行できます。ただし、Node環境とは異なり、ブラウザ環境ではCommonJS Module直接はインポート出来ませんので、`spago bundle-app`コマンドで単一のJavaScriptファイルへと結合する必要があります。`--to`オプションで出力先のファイルを指定することができます。先ほどと同じように、次のようにして新たな雛形を用意しましょう。

```shell-session
$ spago init
$ spago bundle-app --to public/app.js

...

Build succeeded.
Bundle succeeded and output file to public/app.js
```

さて、`public/app.js`が作成されたと思いますので、これを読み込む`public/index.html`も適当に作成しておきます。

```html:public/index.html
<!doctype html>
<script src="app.js"></script>
```

これで、`public/index.html`をブラウザで開くと、コンソールに`🍝`が表示されるはずです。

なお、`spago bundle-app`で結合したコードは、次のようにすればもちろんNode環境でも実行できます。

```shell-session
$ node public/app.js
🍝
```

ここでは`spago budnle-app`でバンドルしましたが、`spago build`でコンパイルしてから、webpackなどを使ってバンドルする方法もあります。PureScriptコンパイラはCommonJS Moduleへとコンパイルするので、JavaScriptのどのバンドラでもバンドルすることができます。






# HalogenによるGUIアプリケーション開発

最後に、ブラウザ環境でのGUIアプリケーション開発も試してみましょう。PureScriptのUIフレームワークでは[`purescript-halogen`](https://github.com/slamdata/purescript-halogen)というのが一番ポピュラーなので、これを使います。これまでと同じようにプロジェクトを作成し、`src/Main.purs`を次のように編集します。

```haskell:src/Main.purs 
module Main where

import Prelude
import Data.Identity (Identity(..))
import Effect (Effect)
import Halogen (mkComponent, mkEval, defaultEval)
import Halogen.Aff (awaitBody, runHalogenAff) 
import Halogen.HTML (text)
import Halogen.VDom.Driver (runUI)

main :: Effect Unit
main = runHalogenAff do
    body <- awaitBody
    runUI (mkComponent
        { initialState: \_ -> {}
        , render: \_ -> text "Hello, World" 
        , eval: mkEval $ defaultEval { handleAction = \(Identity a) -> pure a  }
        }) unit body
```

それから、いつものように`spago install halogen`でHalogenをインストールし、`spago bundle-app`でバンドルしましょう。`index.html`も忘れずに用意します。

```shell-session
$ spago install halogen identity
$ spago bundle-app --to public/app.js
$ echo '<script src="app.js"></script>' > public/index.html
```

あとは、`index.html` を開くと、ページに`Hello World`と書かれていることがわかります。

これで、最低限ながら、PureScriptで本格的なGUIアプリケーションを開発する準備が整いました。このHalogenというフレームワークを使ってどのようにアプリケーションを開発すればいいのかは、また別に記事を書きたいと思っています。




# psc-ide

PureScriptでコンパイラには[psc-ide](https://github.com/purescript/purescript/tree/master/psc-ide)というIDE、つまり統合開発環境が付属していています。『統合開発環境』といってもVisual StudioやEclipseのようなゴッツいものではなくて、コンパイラと連携してコードの問題の修復なんかを自動的に実行するためのインターフェイスを提供するコマンドライン上で動くプログラムです。このpsc-ideを使うための拡張が各種エディタについて提供されています。

* Visual Studio Code: [PureScript IDE](https://marketplace.visualstudio.com/items?itemName=nwolverson.ide-purescript) / [(github)](https://github.com/nwolverson/vscode-ide-purescript)。構文ハイライトをしてくれる[PureScript Language Support](https://marketplace.visualstudio.com/items?itemName=nwolverson.language-purescript)も入れましょう
* Vim: [psc-ide-vim](https://github.com/FrigoEU/psc-ide-vim)
* Atom: [atom-ide-purescript](https://github.com/nwolverson/atom-ide-purescript)
* Emacs: [psc-ide-emacs](https://github.com/epost/psc-ide-emacs)

PureScriptではモジュールがかなり細かく区切られているのでインポートがとにかく面倒くさいのですが、psc-ideを使ったら簡単にインポートを整理できるようになってとても楽になりました。PureScriptを本格的に使うつもりなら、psc-ideはぜひともインストールしましょう。推論された関数の型の型注釈を補間してくれる機能や、カーソルを合わせると関数の型を表示してくれる機能なんかは、入門したてのころは特に役に立つと思います。


# このあとは

* **["PureScript by Example"](https://leanpub.com/purescript/read)** オリジナルの作者philさんが書いたPureScriptの入門書です。無料で読めます。PureScriptを使いたいならまっさきに読みたい資料です。PureScriptに限らず、現代的な関数型プログラミングの一般的な入門としても適しています。SICPみたいな古文書を読んでる場合じゃないぞ！**ただし、まだ内容がPureScript ver.0.11のままで、最新版のコンパイラでは本文のサンプルコードの多くがコンパイルできません。つらい。**コレを私が邦訳した[『実例によるPureScript』](http://aratama.github.io/purescript/)もありますが、こちらも当然v0.11のままなので、更新をお待ちください。
* [purescript/documentation](https://github.com/purescript/documentation) ドキュメント用のリポジトリです。古いサンプルコードが残っているかもしれないので気をつけましょう。

* [Try PureScript!](http://try.purescript.org/) ブラウザ上でPureScriptの実行を試せる素敵サービス。v0.13までは対応しています。
* [QiitaのPureScriptタグ](https://qiita.com/tags/purescript)を漁るのもいいと思います。貴重な日本語記事がいろいろありますし、英語でよければSpagoの作者の[ジャスティンさんもQiitaでたくさん記事を公開](https://qiita.com/kimagure)してくれています。
* [24-days-of-purescript-2016](https://github.com/paf31/24-days-of-purescript-2016) 小ネタ集。記事中のサンプルコードはとうに古くなっていてコンパイル通りませんので、雰囲気だけお楽しみください。つらい。





## 変更履歴



PureScriptもどんどんバージョンアップしてすぐコンパイル通らなくなって困るのですが、この記事はなるべくメンテナンスしたいと思います。もしこの記事の内容が古くなっていてコンパイルできないようなことがあれば、ご連絡いただけると嬉しいです。この記事のコメント欄でもたぶんそのうち気付きますが、<a href="https://twitter.com/cubbit2">ツイッター</a>へご連絡頂いたほうが確実です。

* **2021/04/17** purescript-hyperがあまり更新されておらず、PureScript 0.14になかなか対応しないようなので、purescript-httpureパッケージの紹介に変更しました。私は hyper も尖っていて気に入っていたのですが、httpureのほうが穏便で扱いやすい感じだと思いました。
* **2021/04/13** @YAhiruさんに編集リクエストを頂きました。ありがとうございました 
* **2021/3/28** PureScript 0.14.0 リリース大感謝祭。少なくないパッケージがバージョンアップについていけず振り落とされています。この記事でも、Halogenはすぐアップデートされましたが、Hyperがまだ対応していなかったので注意書きを付け加えました。そのほかは大きな変更はありません。
* **2019/09/24** psc-packageやpulp、Bowerについての記述を削除し、Spagoを使うように内容を改めました。また、Hyper の紹介を復活させました。pursのバージョンは**[v0.13.3](https://github.com/purescript/purescript/releases/tag/v0.13.3)**です。おいおい、この記事一年ぶりの更新かよ……すみません
* **2018/06/12** **[v0.12.0](https://github.com/purescript/purescript/releases/tag/v0.12.0)** に対応するように更新しました。Hyperがv0.12に対応するのがもう少し先になりそうな気がするので、サーバのサンプルの部分はpurescript-node-httpを使うように変えました。purescript-halogen-templateもあまり更新されていないので、ミニマルなサンプルコードに置き換えました。
* **2017/09/24** 全編にわたってpulpを使うように変更し、記事の長さも圧縮しました
* **2017/06/12** 0.11.5 確認作業。この記事に大きな変更はありません
* **2017/04/18** 0.11アップデート作業。あとpurescript-halogen-templeteだけ
* **2017/03/30** 0.11アップデートの注意書きだけ加えました。そのうちまた更新します 
* **2017/01/25** 環境によってコンパイルが通らない問題を修正しました
* **2017/01/07** 記事のメンテナンスをしました。pscのバージョンは**[v0.10.5](https://github.com/purescript/purescript/releases/tag/v0.10.5)**です。バージョンがガンガン上がっていますが、コンパイラに破壊的変更はないので、この記事の内容にも大きな変更はありません。
* **2016/11/12** 記事のメンテナンスをしました。pscのバージョンは**[v0.10.2](https://github.com/purescript/purescript/releases/tag/v0.10.2)**です。node-httpモジュールでAPIの変更があった部分を修正しました。
* この記事を最初に書いた時点での`psc`のバージョンは`0.9.3`でした。