PureScript というプログラミング言語があります。
Haskell のような構文で記述でき JavaScript を出力できる、ということを半年ほど前に知ったのですが。
最近になって、バックエンドを切り替えれば Erlang のソースコードを出力できるということを知りました。
元々は Phoenix のフロントエンドのプログラミングで PureScript を使うための方法を調べようとしていたのですが、あまりに面白そうだったので先にこちらに手を出した次第。
なお、ここから先は PureScript の開発環境は別途準備できている前提で話をしてゆきます。
Alternate backends
PureScript に利用できるバックエンドは、ドキュメントにまとめられています。
Erlang をターゲットにした purerl は今も開発が続けられ、現時点では一つ前のバージョン PureScript 0.15.14 まで対応されています。
Installation
purerl のインストールは、利用する環境のバイナリを GitHub からダウンロードするのが今のところ一番簡単な方法のようです。
ダウンロードできたら、 purerl
コマンドを実行できるようにパスを設定するかリンクを作成するなどします。
purerl を利用した PureScript プロジェクトのサンプル
PureScript のパッケージマネジャの spago を使って、新しい PureScript プロジェクトを作成します。
$ mkdir example $ cd example $ spago init
purerl を設定する
spago.dhall
を編集してバックエンドの指定を追加します。
{ name = "my-project" , packend = "purerl" -- この行を追加する , dependencies = [ "console", "effect", "prelude" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs", "test/**/*.purs" ] }
packages.dhall
を編集して利用するパッケージの指定を purerl のものに変更します。
let upstream = https://github.com/purerl/package-sets/releases/download/erl-0.15.3-20220629/packages.dhall sha256:48ee9f3558c00e234eae6b8f23b4b8b66eb9715c7f2154864e1e425042a0723b
spago run
コマンドを実行すると、パッケージのインストールやビルドが実行され、 src/Main.purs
に書かれたコードの実行結果が出力されることを確認できると思います。
$ spago run ... パッケージのインストールやビルドのログ ... 🍝
生成された Erlang のコードは output/
に出力されています。
$ ls output/Main/ corefn.json externs.cbor main.hrl main@foreign.hrl main@ps.erl
また .beam ファイルは ebin/
に出力されます。
src/Main.purs
のコンパイル結果は main@ps.beam
に出力されています。
$ ls ebin/main* ebin/main@ps.beam
インストールされたパッケージのソースコードやバイナリも output/
と ebin/
に格納されていることが確認できると思います。
Erlang から利用する
erl
を起動します。
このとき、検索対象のパスに .beam ファイルが格納されたディレクトリを指定します。
$ erl -pa ebin
.beam ファイルのファイル名から、モジュール名は main@ps
とわかるので、main@ps:main
を実行してみます。
1> main@ps:main(). #Fun<effect_console@foreign.0.108104793>
main
の型は Effect Unit
と定義されていますが、 Effect
モナドの値は Erlang からは関数に見えるようです。
戻り値の関数を実行してみます。
2> (main@ps:main())(). 🍝 ok
Elixir から利用する
iex
からも利用しています。
やり方は erl
と同じです。
$ iex -pa ebin
iex(1)> :main@ps.main() #Function<0.108104793/0 in :effect_console@foreign.log/1>
iex(2)> :main@ps.main().() 🍝 :ok
elixir
コマンドで直接実行することもできます。
$ elixir -pa ebin -e ':main@ps.main().()' 🍝
purerl を利用した Elixir プロジェクトのサンプル
Hex を検索すると、Elixir から purerl を利用するためのパッケージ purerlex を登録してくださっている方がいます。
これを利用させてもらうことにしました。
まず Elixir のプロジェクトを作成します。
$ mix new my_app $ cd my_app
続いて同じディレクトリで PureScript のプロジェクトを作成します。
$ spago init
先の purerl のサンプルと同様に spago.dhall
と packages.dhall
を編集してバックエンドとパッケージの取得先を指定します。
purerlex を利用する
mix.exs
を次のように編集します。
defmodule MyApp.MixProject do use Mix.Project def project do [ app: :my_app, version: "0.1.0", elixir: "~> 1.16", start_permanent: Mix.env() == :prod, erlc_paths: ["output"], # 追加: Erlang のソースコードのパスとして output を指定 compilers: [:purerl] ++ Mix.compilers(), # 追加: Elixir のコンパイル時に purerl の実行を指定 deps: deps() ] end def application do [ extra_applications: [:logger] ] end defp deps do [ {:purerlex, "~> 0.12.2"} # 追加 ] end end
ちなみに。
ここでコンパイラに指定している purerl
は、 purerl
コマンドではなく、purerlex
が定義しているタスクです。
パッケージを取得します。
$ mix deps.get
iex
を起動します。
$ iex -S mix
-S mix
を指定して iex
を起動すると自動的にコンパイルが実行されますが、このとき PureScript のパッケージの取得やビルドも実行されていることが確認できると思います。
iex(1)> :main@ps.main() #Function<0.108104793/0 in :effect_console@foreign.log/1> iex(2)> :main@ps.main().() 🍝 :ok
Elixir から利用する
Elixir のプロジェクトを作成したときに作成される MyApp.hello/0
を PureScript で書いたものに置き換えてみます。
MyApp.hello/0
はアトムを返すので、アトムを利用できるように PureScript のパッケージを追加します。
このパッケージは packages.dhall
で指定した package-sets を元に検索されます。
$ spago install purescript-erl-atom
新しく src/MyApp.purs
を作成します。
module MyApp where import Erl.Atom (atom) hello = atom "world"
$ mix compile
コンパイルの結果出力される .beam ファイルが格納される _build/dev/lib/my_app/ebin/
を確認すると myApp@ps.beam
という名前で出力されていることがわかります。
実行してみます。
$ iex -S mix iex(1)> :myApp@ps.hello() :world
期待する値が得られることが確認できました。
MyApp
の呼び出しを委譲してみます。
defmodule MyApp do defdelegate hello, to: :myApp@ps end
当然ですが期待する結果がえられますしテストもパスします。
$ iex -S mix iex(1)> MyApp.hello() :world
$ mix test 1 test, 0 failures
Elixir のプロジェクトで PureScript を利用できることが確認できました。