がんばるぞ

がんばります

1ヶ月間姿勢が悪かったのでPureScriptの勉強をしているよという話をします

この記事はスターフェスティバル Advent Calendar 2021の5日目です。


姿勢

1ヶ月間良い姿勢で仕事した結果、腰痛が治るわ睡眠効率が改善するわ最高!という話をするつもりだったのだけど、この一ヶ月間全くもって姿勢が良くなかった。 一体何が悪かったのだろうか...。いや、悪かったのは姿勢なんだけど、なぜ良い姿勢を保つことができなかったのか。

終わってしまったことを悔やんでも仕方がないので、今日はなにか別の話をすることにします。最近PureScriptで関数指向プログラミングの勉強をし始めたのでそこらへんの話を。 腰が痛いな

ちなみにやっとコードが読めるようになってきたくらいで関数指向とはみたいなところはほとんど理解出来ていないし、モナド周りでめっちゃ苦戦してます。 背中も違和感があるな

PureScript とは?

AltJSの純粋関数型言語Haskellから多くの影響を受けているらしいです。 同じくAltJSで関数指向のプログラミングが出来るElmという言語もありますが、サーバーサイドで動作させることも可能で言語機能も充実しているという点がElmとは異なるようです。

なぜPureScriptなのか

HaskellScalaあたりも候補だったのですが、

  • spagoというコンパイラ&パッケージマネージャが使いやすそうだったこと
  • npmとnodejsがあればとりあえず始められること
  • どうせやるなら純粋関数型まで振り切ってしまった方がいい気がした

あたりを理由にPureScriptにしました。

環境構築周りの話

こちらの記事が大変参考になる上にめちゃくちゃいい感じに要約されているので、こちらを読むとわかりやすいです。 qiita.com

面白かったところたち

PureScriptの解説がちゃんとできるほど理解できてないので、勉強していておもしろーいってなったところをつらつら紹介したいと思います

デフォルトで部分適用される関数

例えば以下のコードは、PHPerであれば one という変数に 1 を代入していると読むと思うのですが、実はこれは1という値を返す関数の定義になります

one = 1

引数が必要な場合は以下のように書きます。

add x y = x + y

また、型の書き方もPHPとは全然違います。 先ほどの add 関数であれば、型は以下の通りです。

add :: Int -> Int -> Int

最初の Int が 第一引数の型で、2つ目の Int が第二引数の型で、最後の Int が返り値の型です。 なんで Int Int -> Int じゃないんだ?と僕は思いましたが、当然理由があって、PureScriptでは全ての関数は1つの引数だけ受け取るようになっているので、引数が二つある関数は実際には「関数を返す関数」なんですね。引数が3つある関数は「関数を返す関数を返す関数」です。

具体的には add 1 3 などのようにadd関数を実行すると、 まず add 1 の部分が評価された結果 Int -> Int という関数が返り、その関数に 3 が適用されることで最終的に 4 という数値が手に入る。みたいな感じです。

replなどでその様子を確かめるとわかりやすいです( :t は型を調べるコマンドです)

> :paste
… add :: Int -> Int -> Int
… add x y = x + y
… 
> :t add                  
Int -> Int -> Int

> :t add 1
Int -> Int

> :t add 1 3
Int

> (add 1) 3
4

ここらへんの挙動もPHPでは考えられない世界観で面白いですね。

無理やりPHPで表すとこんな感じでしょうか

<?php
$add = fn (int $x) => fn (int $y) => $x + $y;

$add(1)(3);

たぶんこれのおかげで関数の合成や組み合わせがしやすくなるとか再利用生が上がるとかなんか色々メリットがあるのだと思われです。わかりませんけど。

中置演算子

+ などの記号は実際のところただの関数で、型は forall (a :: Type). Semiring a => a -> a -> a です。 ただ、普通の関数と違い引数を記号の左右に書くことができます。こういったものを中置演算子と呼ぶらしいです。

普通の関数と同じように扱うためには () で囲みます。

> (+) 1 3
4

また、逆に普通の関数を `` で囲むことで中置演算子として書くことができます。

> 1 `add` 3
4

infix という機能を使うと、この中置演算子を自由に追加することができるようです。

> infixl 6 add as 🍎 
> 1 🍎 2
3

自由に演算子を追加できるという世界観もPHPには存在しないので面白いですね。

ただかなり気を付けないと可読性が悪化するだけになりそうなので、フレームワークを作るなどでない限り出番は無さそうな気がしています

パターンマッチ

PHPも最近はmatch式が導入されて便利ですが、パターンマッチはもっと便利です。

例えばEitherという成功(Right)あるいは失敗(Left)を表す型があるのですが、以下のような感じで成功時と失敗時の処理を分岐させることができます。 (関数のオーバーロードに少し似てますね)

data Err = ConnectionErr | Timeout

logError :: Either Err String -> Effect Unit
logError (Right _) = log "success"
logError (Left _) = log "error"

PHPで書くならこんな感じでしょうか

<?php

return match ($either::class) {
    Right::class => fn() => print 'success',
    Left::class => fn() => print 'error',
};

さらに、もっと細かく型を指定した書き方もできます。以下だとConnectionErrだった時だけ違うログメッセージ出すってかんじですね。

これくらいの分岐からPHPだと若干ごちゃついてきますが、PureScriptだととてもシンプルです。

logError (Right _) = log "success"
logError (Left ConnectionErr) = log "connection error"
logError (Left _) = log "error"

そして最高なのが、全てのパターンを網羅できていない場合はビルドできなくなることです。

PHPenumが入ったことだし、PHPStanとかを使えばEnumを使ったswitch文とかmatch式は全部のパターンを網羅できてないことを検知できるようにそのうちなりそうですね。もうなってるのかな

おわりに

ちょっと早い上にとりとめもない感じなのですが娘と寝る時間になったのでもう終わります。 純粋関数型言語の世界観はPHPとは全く違うので慣れるまで大変ですね。全体的な構文もそうですが、do記法とかモナドは意味がわからないし、関数合成も慣れないと挙動が想像しにくいです。

でもこれらを理解することで得られる学びにすごく興味があるので引き続きチビチビがんばっていきたいと思います。

あと来年こそは良い姿勢で過ごすことで体のコリを軽減し睡眠の質を改善したいです。 今年はもう諦めました。