Edited at

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


はじめに

スクリプト言語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


プロジェクトの作成

次はプロジェクトを作成しましょう。どこかに空のディレクトリを作成してそこに移動し、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というファイルを開いてみると、次のようなコードが書かれているのがわかるかと思います。


src/Main.purs

module Main where

import Prelude

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

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


……スパゲッティの絵文字!?4


ビルドと実行

それでは、このサンプルプロジェクトを動かしてみましょう。アプリケーションを起動するには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はこのプロジェクトの設定を記述するファイルですが、これを開いてみると次のように書いてあると思います。


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コマンドで見ることができます。


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

次は、hyperというパッケージを使って、アクセスするとHello, Hyper!とだけ返す簡単なサーバサイドウェブアプリケーションを作ってみます。さきほど作ったひな形のプロジェクトのsrc/Main.pursを編集して、次のようなコードを記述します。


src/Main.purs

module Main where

import Prelude
import Control.Monad.Indexed.Qualified as Ix
import Effect (Effect)
import Hyper.Node.Server (defaultOptionsWithLogging, runServer)
import Hyper.Response (closeHeaders, respond, writeStatus)
import Hyper.Status (statusOK)

main :: Effect Unit
main = runServer defaultOptionsWithLogging {} Ix.do
writeStatus statusOK
closeHeaders
respond "Hello, Hyper!"


このコードは、Webサーバを起動し、リクエストがあったらHello, Hyper!とレスポンスを返すだけのプログラムです。runServerでサーバを作成し、リクエストの読み取りを開始しています。そしてリクエストが来たらIx.do以下の部分が実行され、writeStatusでステータスコードを書き込み、 respondでレスポンスボディを書き込みます。『純粋関数型プログラミング言語』といえど、素のNode/JavaScriptで簡単なHTTPサーバを書くときと似たような流れで記述できるのが、なんとなくわかると思います。

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

$ spago install hyper


...

$ spago run

...

* Build successful.
> Listening on http://0.0.0.0:3000

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


ブラウザ環境での実行

次は、ブラウザ環境で簡単なPureScriptアプリケーションを実行してみましょう。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も適当に作成しておきます。


public/index.html

<!doctype html>

<script src="app.js"></script>

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

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

$ node public/app.js

Hello, world!

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


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

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


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

$ 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を使うための拡張が各種エディタについて提供されています。

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


このあとは



  • "PureScript by Example" オリジナルの作者philさんが書いたPureScriptの入門書です。無料で読めます。PureScriptを使いたいならまっさきに読みたい。PureScriptに限らず、現代的な関数型プログラミングの一般的な入門としても適しています。SICPみたいな古文書を読んでる場合じゃないぞ!ただし、まだ内容がPureScript ver.0.11のままで、最新版のver. 0.12のコンパイラでは本文のサンプルコードの多くがコンパイルできません。つらい。コレを私が邦訳した『実例によるPureScript』もありますが、こちらも当然v0.11のままなので、更新をお待ちください。


  • purescript/documentation ドキュメント用のリポジトリです。古いサンプルコードが残っているかもしれないので気をつけましょう。


  • 24-days-of-purescript-2016 小ネタ集。記事中のサンプルコードはとうに古くなっていてコンパイル通りませんので、雰囲気だけお楽しみください。つらい。


  • Try PureScript! ブラウザ上でPureScriptの実行を試せる素敵サービス。v0.11対応です。つらい。

  • あとはQiitaのPureScriptタグを漁りましょう。貴重な日本語記事がいろいろありますし、英語でよければSpagoの作者のジャスティンさんもQiitaでたくさん記事を公開してくれています。


変更履歴

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



  • 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でした。





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



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



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



  4. Spagoのプロジェクトを開始したジャスティンさんは、ツールやライブラリに食べ物の名前を付けるのが好きみたいです。ツイッターにもよく食べ物の画像を流しています。 



  5. なお、私の環境では絵文字がちゃんと表示されず、になってしまいました。かなしい。