"PureScript is more like Haskell than Haskell itself." – `@thebeardedzug
— Rahul Goma Phulore (@missingfaktor) 2017年3月24日
はじめに
スクリプト言語**PureScript**の開発環境構築から簡単なアプリケーションの作成までの手順を紹介します。この記事では、基本的なコマンドライン環境のアプリケーションだけでなく、Halogenというユーザインターフェイスのフレームワークや、簡単なサーバサイドアプリケーションを動かすところまでを紹介しています。そのため、シングルページアプリケーションのような実用性の高いアプリケーションを作って試せるようになっています。ブラウザ環境で動けば、ウェブページに組み込んで自分が作ったアプリケーションを簡単に他の人に見せられますし、Electronを使って単独で動くウィンドウアプリケーションなんかも作ることができるなど、夢が広がりますね。
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**をインストールしてください123。
$ npm install --global purescript spago
もしかしたら、環境によっては次のようなエラーメッセージでうまくいかないかもしれません。
error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
PureScriptコンパイラはncurses
というライブラリに依存しているそうで、次のようにしてlibncurses5
をインストールすればいいかもしれません。
$ sudo apt update
$ sudo apt install libncurses5
プロジェクトの作成
次はプロジェクトを作成しましょう。どこかに空のディレクトリを作成してそこに移動し、spago init
コマンドを実行します。ここでは、hello
ディレクトリを作って、その中でspago init
を実行しています。
$ mkdir hello
$ cd hello
$ spago init
成功すると、次のような構成のサンプルプロジェクトが作成されると思います。
.
├── packages.dhall
├── spago.dhall
├── src
│ └── Main.purs
└── test
└── Main.purs
src
がPureScriptソースコードを保存するディレクトリです。また、.purs
という拡張子のファイルがPureScriptソースファイルの拡張子です。 src/Main.purs
というファイルを開いてみると、次のようなコードが書かれているのがわかるかと思います4。
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = do
log "🍝"
ビルドと実行
それでは、このサンプルプロジェクトを動かしてみましょう。アプリケーションを起動するには**spago run
コマンド**を実行します。このとき、自動的に再ビルドも行われます。
$ 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
ディレクトリ以下に出力されているはずです。それからアプリケーションが起動して、🍝
が出力されたことがわかります5。
なお、プログラムの実行はせずにビルドだけを行うには、spago build
コマンドを使います。また、テストを実施するには**spago test
コマンド**を使います。spago test
コマンドを実行すると、src/Main.purs
にあるMain
モジュールではなく、test/Main.purs
ファイルのTest.Main
モジュールがコンパイルされて実行されます。
パッケージの追加
PureScriptコンパイラに付属するモジュールは、最低限のデータ型を定義するモジュールであるPrimと、型レベル計算の基礎となる型クラスであるPrim.Boolean
などのモジュールのみです。他には一切のライブラリが含まれず、言語組み込みのどんな関数も演算子も存在しないため、コンパイラのみでは標準出力はおろか、足し算すらできません。さきほどはspagoによってサンプルプロジェクトに必要なパッケージがもとから記述されていたためコンパイルが成功しましたが、今後開発が進むと必要なパッケージを手動でインストールすることになるでしょう。その場合も、spagoを使ってPureScriptパッケージをインストールします。
spagoでパッケージを追加するには、spago install
コマンドを実行します。たとえば、purescript-halogen
というライブラリをインストールしたければ、spago install halogen
というコマンドを実行します。PureScriptのライブラリにはたいてい purescript-
というプリフィックスが付けられていますが、spago install
するときにはpurescript-
は不要です。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
を実行してみましょう。
$ 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というドキュメント検索サービスを使うのがいいでしょう。pursuitに登録されていないライブラリも結構あるので、githubで探してみるのもいいと思います。
なお、spago install
で直接インストールできるパッケージは、package-setsに含まれているパッケージだけです。大抵はそれだけで問題ないと思いますが、package-setsに含まれないようなパッケージをインストールするには別の設定が必要になります。この記事ではそのあたりの説明は省略しますので、詳しくはspagoのドキュメントを参照してみてください。spago install
でインストールできるパッケージの一覧は、spago list-packages
コマンドで見ることができます。
httpureによるサーバーサイドアプリケーションの開発
次は、httpureというパッケージを使って、アクセスするとhello world!
とだけ返す簡単なサーバサイドウェブアプリケーションを作ってみます。
さきほど作ったひな形のプロジェクトの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
でアプリケーションを起動してみましょう。
$ 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
オプションで出力先のファイルを指定することができます。先ほどと同じように、次のようにして新たな雛形を用意しましょう。
$ 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
も適当に作成しておきます。
<!doctype html>
<script src="app.js"></script>
これで、public/index.html
をブラウザで開くと、コンソールに🍝
が表示されるはずです。
なお、spago bundle-app
で結合したコードは、次のようにすればもちろんNode環境でも実行できます。
$ node public/app.js
🍝
ここではspago budnle-app
でバンドルしましたが、spago build
でコンパイルしてから、webpackなどを使ってバンドルする方法もあります。PureScriptコンパイラはCommonJS Moduleへとコンパイルするので、JavaScriptのどのバンドラでもバンドルすることができます。
HalogenによるGUIアプリケーション開発
最後に、ブラウザ環境でのGUIアプリケーション開発も試してみましょう。PureScriptのUIフレームワークではpurescript-halogen
というのが一番ポピュラーなので、これを使います。これまでと同じようにプロジェクトを作成し、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
も忘れずに用意します。
$ 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というIDE、つまり統合開発環境が付属していています。『統合開発環境』といってもVisual StudioやEclipseのようなゴッツいものではなくて、コンパイラと連携してコードの問題の修復なんかを自動的に実行するためのインターフェイスを提供するコマンドライン上で動くプログラムです。このpsc-ideを使うための拡張が各種エディタについて提供されています。
- Visual Studio Code: PureScript IDE / (github)。構文ハイライトをしてくれるPureScript Language Supportも入れましょう
- Vim: psc-ide-vim
- Atom: atom-ide-purescript
- Emacs: psc-ide-emacs
PureScriptではモジュールがかなり細かく区切られているのでインポートがとにかく面倒くさいのですが、psc-ideを使ったら簡単にインポートを整理できるようになってとても楽になりました。PureScriptを本格的に使うつもりなら、psc-ideはぜひともインストールしましょう。推論された関数の型の型注釈を補間してくれる機能や、カーソルを合わせると関数の型を表示してくれる機能なんかは、入門したてのころは特に役に立つと思います。
このあとは
-
"PureScript by Example" オリジナルの作者philさんが書いたPureScriptの入門書です。無料で読めます。PureScriptを使いたいならまっさきに読みたい資料です。PureScriptに限らず、現代的な関数型プログラミングの一般的な入門としても適しています。SICPみたいな古文書を読んでる場合じゃないぞ!**ただし、まだ内容がPureScript ver.0.11のままで、最新版のコンパイラでは本文のサンプルコードの多くがコンパイルできません。つらい。**コレを私が邦訳した『実例によるPureScript』もありますが、こちらも当然v0.11のままなので、更新をお待ちください。
-
purescript/documentation ドキュメント用のリポジトリです。古いサンプルコードが残っているかもしれないので気をつけましょう。
-
Try PureScript! ブラウザ上でPureScriptの実行を試せる素敵サービス。v0.13までは対応しています。
-
QiitaのPureScriptタグを漁るのもいいと思います。貴重な日本語記事がいろいろありますし、英語でよければSpagoの作者のジャスティンさんもQiitaでたくさん記事を公開してくれています。
-
24-days-of-purescript-2016 小ネタ集。記事中のサンプルコードはとうに古くなっていてコンパイル通りませんので、雰囲気だけお楽しみください。つらい。
変更履歴
PureScriptもどんどんバージョンアップしてすぐコンパイル通らなくなって困るのですが、この記事はなるべくメンテナンスしたいと思います。もしこの記事の内容が古くなっていてコンパイルできないようなことがあれば、ご連絡いただけると嬉しいです。この記事のコメント欄でもたぶんそのうち気付きますが、ツイッターへご連絡頂いたほうが確実です。
- 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**です。おいおい、この記事一年ぶりの更新かよ……すみません
- 2018/06/12 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**です。バージョンがガンガン上がっていますが、コンパイラに破壊的変更はないので、この記事の内容にも大きな変更はありません。
- 2016/11/12 記事のメンテナンスをしました。pscのバージョンは**v0.10.2**です。node-httpモジュールでAPIの変更があった部分を修正しました。
- この記事を最初に書いた時点での
psc
のバージョンは0.9.3
でした。
-
ここでは
--global
オプションをつけてグローバルにインストールしていますが、もちろんお好みでローカルにインストールしても構いません。 ↩ -
PureScriptでは以前はパッケージマネージャとしてBowerが使われており、またpsc-packageというツールの開発も試験的に行われていましたが、Spagoが登場したことにより、今後はBowerからSpagoへと移行していくのではないかと思われます。現状でもBowerを使うのが前提になっているライブラリも多いですが、Spagoはそうしたライブラリでもそのまま問題なく扱えるので、この記事ではSpagoを使って説明していくことにしました。なお、Bowerは非推奨のツールになったというだけで、Bowerではパッケージ管理に何か致命的な不具合があるということではなく、Bowerも使おうと思えば使うことができます。 ↩
-
また、以前この記事ではpulpというビルドツールを扱っていましたが、Spagoがパッケージマネージャとビルドツールを兼ねたツールなので、pulpについての説明はこの記事から省きました。pulpが使えなくなったわけではありませんので、spagoで何か都合が悪いようであればpulpも試してみてください。また、pursuitでパッケージを公開するにはpulpが必要だそうなので、pulpが不要になったわけではないですし、完全にbowerから足を洗うことができたわけでもないようです。 ↩
-
出力されるのはなぜかスパゲッティの絵文字です。Spagoのプロジェクトを開始したジャスティンさんは、ツールやライブラリに食べ物の名前を付けるのが好きみたいです。ツイッターにもよく食べ物の画像を流しています。 ↩
-
なお、私の環境では絵文字がちゃんと表示されず、
�
になってしまいました。かなしい。 ↩