Purescript

Haskellの記事を色々見て復習してたんだ。オイラーの脳内メモリーはDRAMですよ。 常にリフレッシュしないと蒸発しちゃうからね。

ついこの間も一般人が見るような所に、記事が載ってたぞ。 Haskell こういうのをきっかけに、数学に再トライするのもよかろう。Pythonばかりが数学を 解く道具じゃないからね。多様性は善、進化に必要。

そう言えば、図書館の新刊コーナーに置いてあった、 『工学部ヒラノ教授のはじまりの場所』 (今野浩)っての、面白かったな。シリーズに なってるようなので、色々借りてこよう。(wikipediaからのリンクでも色々読めて面白いぞ)

で、とある人がHaskell似のPureScriptってのがいいよって叫んでいた。ちょいと箸休めで 箸を伸ばしてみるかな。資料は、下記ぐらい。

A strongly-typed functional programming language that compiles to JavaScript

PureScript Advent Calendar 2016

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

どこの陣地に入れる? PureScriptってのは、Haskell似のスクリプトをコンパイルしてJavascriptにするらしい。ならば、Web系に強い ClearLinuxが良かろう。 バンドルを探ってみたら、上手い具合に nodejs-basicってのが有った。こいつを入れるだけで、npmとかいうパッケージマネージャが使えるのかな? 悩むより手を動かせ。

sakae@clr:~$ node -v
v7.10.0
sakae@clr:~$ npm install  purescript                                            
> purescript@0.11.5 postinstall /home/sakae/node_modules/purescript             > node lib/install.js

   The `/home/sakae/node_modules/purescript/vendor/purs` binary doesn't seem to work correctly
   pre-build test failed
   compiling from source
   built successfully
/home/sakae
└─┬ purescript@0.11.5
  ├─┬ bin-build@2.2.0
  :
  └── to-executable-name@1.1.0

npm WARN enoent ENOENT: no such file or directory, open '/home/sakae/package.json'
npm WARN sakae No description
npm WARN sakae No repository field.
npm WARN sakae No README data
npm WARN sakae No license field.

コンパイルに随分時間がかかる(30分ぐらい)と思ったら、haskell君が裏で健闘してたぞ。

sakae@clr:~$ ps aocmd
node lib/install.js
stack install --allow-different-user --local-bin-path /home/sakae/node_modules/p
/home/sakae/.stack/setup-exe-cache/x86_64-linux-tinfo6/setup-Simple-Cabal-1.24.2
/home/sakae/.stack/programs/x86_64-linux/ghc-tinfo6-8.0.2/lib/ghc-8.0.2/bin/ghc
ps aocmd

続いて、PureScript用のパッケージを取り寄せるためのプログラム(pythonで言うpip)を インストールします。難しい事は無く、npm install bower するだけみたい。 これで、取り合えずの用意が出来たんで、node_modules/.bin/ にPATHを通しておきます。

それから、何はなくともハロワです。適当なdirを作ってその中で作業するという、世間一般に 言われてるプロジェクトなるものを作ります。大げさ自慢だな。

sakae@clr:~/hello$ bower install --save purescript-console
bower cached        https://github.com/purescript/purescript-console.git#3.0.0
bower validate      3.0.0 against https://github.com/purescript/purescript-console.git#*
  :
purescript-prelude#3.1.0 bower_components/purescript-prelude
sakae@clr:~/hello$ mkdir src
sakae@clr:~/hello$ cat -- > src/Main.purs
module Main where

import Prelude
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)

main :: Eff (console :: CONSOLE) Unit
main = log "Hello, world!"
Ctl+D    ;;コピペの終了を指示

purescript-consoleってのが、出力用のパッケージだそうな。こういう基本的なやつも コンパイラーとは別になってるとな。

続いて、src-dirを作り、Webからのスクリプトを貼り込みます。なんか、Haskellのそれと 似てますなあ。

sakae@clr:~/hello$ purs compile "bower_components/purescript-*/src/**/*.purs" "src/**/*.purs"
Compiling Data.Show
Compiling Data.NaturalTransformation
Compiling Data.Boolean
  :
Compiling Prelude
Compiling Control.Monad.Eff.Class
Compiling Main

コンパイルの成果物は、outputの中に置かれるとな。

sakae@clr:~/hello$ ls output/
Control.Applicative          Data.BooleanAlgebra         Data.Ordering
Control.Apply                Data.Bounded                Data.Ord.Unsafe
Control.Bind                 Data.CommutativeRing        Data.Ring
Control.Category             Data.DivisionRing           Data.Semigroup
Control.Monad                Data.Eq                     Data.Semiring
Control.Monad.Eff            Data.EuclideanRing          Data.Show
Control.Monad.Eff.Class      Data.Field                  Data.Unit
Control.Monad.Eff.Console    Data.Function               Data.Void
Control.Monad.Eff.Uncurried  Data.Functor                Main
Control.Monad.Eff.Unsafe     Data.HeytingAlgebra         Prelude
Control.Semigroupoid         Data.NaturalTransformation
Data.Boolean                 Data.Ord

目指すはMainの中に出来たindex.js それをNode環境で走らせる。

sakae@clr:~/hello$ node
> const Main = require('./output/Main/index')
undefined
> Main.main()
Hello, world!
{}

これが基本の流れみたいだ。オイラーが嬉しいと思うのは、Haskellなんかに比べて小ぶりな パッケージが揃っている事。

Pursuit is the home of PureScript documentation

bodil/pulp

先の例だと、自分でプロジェクトdirを作ったけど、そしてコンパイルも面倒だった。で、面倒 嫌いな現代人用に誰かが苦労し、その分け前が公開されてる。

bodil/pulp

pulpを入れた時点での関係者達

sakae@clr:~$ npm list --depth 0
/home/sakae
├── bower@1.8.0
├── pulp@11.0.0
└── purescript@0.11.5
sakae@clr:~$ ls node_modules/.bin/
acorn              find-versions          pulp         static
bin-version-check  insert-module-globals  purs         strip-dirs
bower              JSONStream             rc           strip-indent
browserify         lpad-align             rimraf       tree-kill
browserifyinc      miller-rabin           seek-bunzip  umd
browser-pack       mime                   seek-table   which
deps-sort          mkdirp                 semver
executable         module-deps            sha.js

早速、使ってみる。

sakae@clr:/tmp$ mkdir hoge
sakae@clr:/tmp$ cd hoge/
sakae@clr:/tmp/hoge$ pulp init
  :
purescript-psci-support#3.0.0 bower_components/purescript-psci-support
└── purescript-console#3.0.0
sakae@clr:/tmp/hoge$ pulp run
  :
Compiling Main
Compiling PSCI.Support
* Build successful.
Hello sailor!

これで終わりにしようかと思ったけど、pulpのgithubを見てたら、簡単にWebから 出来栄えを確認出来ると書いてあった。曰く

To see how this works, let's set up a project for serving the default
hello world app through pulp server.

$ mkdir hello-server
$ cd hello-server
$ pulp init

We need an index.html file to load our compiled PureScript code.
Place this in your new hello-server folder:

<!doctype html>
<html>
  <body>
    <h1>Hello sailor!</h1>
    <script src="/app.js"></script>
  </body>
</html>
Now, start the server:

$ pulp server

It will tell you that it's launched a web server at http://localhost:1337/,
and after a little while it will tell you that it's finished compiling
(bundle is now VALID). If you browse to http://localhost:1337/, you should,
in addition to the "Hello sailor!" header on the webpage, see that your
PureScript code has printed the text "Hello sailor!" to the console.

でも、ClearLinuxには、localhostにブラウザーなんて入っていないしなあ。成果物を ブラウザーのある所へexportして確認するのも馬鹿げてる。で、数秒考えた後に、上手い 方法を思いついた。

vboxにポートフォワーディング機能が有るじゃん。それを使って localhost:1337を localhost:8080へ転送しちゃえ。そうすれば、Windows10に入ってるブラウザーは、 あたかもClearLinuxに入ってるように振る舞うぞ。

sakae@clr:~/hello-server$ pulp server
* Server listening on http://localhost:1337/
* Building project in /home/sakae/hello-server
* Build successful.
* Bundling JavaScript...
* Bundled.

こうしておいて、http://localhost:8080/ へアクセスするも、応答無しというつれない 返事がブラウザーよりありました。

こういう時は、慌てずに調査です。output/app.jsが出来上がってました。これ、nodeから 叩けるそうなので、

sakae@clr:~/hello-server$ node output/app.js
Hello sailor!

出来上がったのは、どうやら動いている風です。そうすると、そのapp.jsをブラウザーが ロードするindex.htmlがちゃんと動くかな。/app.jsを取ってこいという事は、output直下が 昔の言葉でいうドキュメントルートになるはず。

index.htmlをoutputの中に持って行って実行してみるも、やはり応答無し。 となると、ポートフォワーディングが上手くいかないのかな?それとも、サーバーが たたないのかな?

代替のサーバーを、pythonで動かして確認しよう。

sakae@clr:~/hello-server/output$ python3 -m http.server 1337

こうやっておいて、Windows10側から、localhost:8080/ をアクセスすると、ちゃんと アクセス出来たぞ。これで、ポートフォワーディングはvboxがちゃんと取り計らってくれてる 事が確認出来た。

次は、pulpがちゃんとサーバーを上げてるか?

sakae@clr:~$ netstat -na
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.1:1337          0.0.0.0:*               LISTEN

ちゃんと聞き耳をたててるようです。

curlで確認します。

sakae@clr:~$ curl http://localhost:1337/
<!doctype html>
  :
</html>
sakae@clr:~$ curl http://localhost:1337/
sakae@clr:~$ curl http://localhost:1337/
curl: (7) Failed to connect to localhost port 1337: Connection refused

最初のアクセスは、index.htmlを指示通り、プロジェクト-dirの直下に置いた場合。 二度目のアクセスは、それを消した場合。三度目のアクセスは、サーバーを落とした場合 です。

ここまでを総合すると、サーバーは、プロジェクト直下のindex.htmlを見て動いてる。 直下がドキュメントルートだとすると、output/app.jsになるはずだな。そういう設定を 行って、アクセスするも、やはりエラーがです。接続がリセットされましたですって。 もうわけわかめ。(尚、Windowsでのlocalhost指定は、ブラウザーによって挙動が違うので、 IP直指定が確実。これTipsだな。)

pulp server は、app.jsを作る事に専念させ、サーバー業務はpythonに担ってもらおう。 ちょいと情けないけどね。それとも、作者にご相談する? まてまて、折角の機会だから、ソースぐらい見ておけ。Server.purs

data BuildResult
  = Succeeded
  | Failed

getBundleFileName :: Options -> AffN String
getBundleFileName opts =
  (_ <> "/app.js") <$> getOption' "buildPath" opts

決め打ちと言うか、特に指定が無い場合のデフォルトかしら。そして、

action :: Action
action = Action \args -> do
  let opts = Map.union args.globalOpts args.commandOpts
  out <- getOutputter args

  bundleFileName <- getBundleFileName opts
  hostname <- getOption' "host" opts
  port <- getOption' "port" opts

  -- This AVar should be 'full' (i.e. contain a value) if and only if the
  -- most recent build attempt has finished; in this case, the value inside
  -- tells you whether the build was successful and the bundle can be served
  -- to the client.
  rebuildV <- AVar.makeVar

  server <- liftEff $ createServer rebuildV bundleFileName
  listen server { hostname, port, backlog: Nothing }

  out.log $ "Server listening on http://" <> hostname <> ":" <> show port <> "/\
"

読めそうで読めない、方言が有るな。これはもう最初からやらにとだめだな。そんなあなたに お勧めのとっておきの資料は、

PureScript by Example

実例によるPureScript(上記の日本語訳)

そして、太っ腹な事に、上記の本のテキストやcodeが、下記に公開されてます。 purescript-book

exec

早速コードを実行してみるか。基本的には、下記のようにやるようだ。

sakae@clr:~$ cd chapter3/
sakae@clr:~$ bower update
sakae@clr:~$ pulp build

盛大なエラーが出て来るな。エラー潰しにtoolが欲しい。そうだ、emacsにお願いしよう。

purescript-modepsc-ide-emacsを入れる。

M-. で、定義に飛んでいけるのがとんでもなく便利だ。

そして、対象を広げてみるか。この間入れた Debian 9 を土台にしよう。でも、基礎となるnodeは、セキュリティの関係で、下記の ようにするのが良いそうだ。nodeも足が速いけど、2019年まではサポートされるLTS版 らしい。

curl -sL https://deb.nodesource.com/setup_6.x | sudo bash -
sudo apt install nodejs
deb9:~$ node -v
v6.11.0
deb9:~$ npm -v
3.10.10

purescriptのコンパイラーは、バイナリー版を入れた。後は、nodeの関係だな。

npm install pulp bower

Windows版Linux

どう使う?Windows版Linux

これ、雑誌の抜粋かな? 詳しい事は、雑誌でとかの宣伝臭が無いね。