purescript-bucketchainの紹介
はじめに
rackやexpress, koaのようなウェブサーバーのための共通のインターフェースを提供するライブラリをつくりました。 それがpurescript-bucketchainです。
背景
外部のnpm packageに頼らず、ランタイムがnodejsなのでパイプで上手くやってくれるようにストリームベースなインターフェースで、koaとかexpressっぽさを踏襲しつつ、ミドルウェアを積みまくってサーバーを構築できるような基盤がほしくなりました。
使い方の流れ
ミドルウェアを用意する
特定のpathで特定の画像を返すミドルウェアをつくってみましょう。 まずは以下を見てください。
middleware :: Middleware middleware next = do http <- ask if requestMethod http == "GET" && requestURL http == "/img" then liftEffect do setStatusCode http 200 setHeader http "Content-Type" "image/png" Just <<< fromReadable <$> createReadStream "example/300x300.png" else next
Middleware
はHandler (Maybe ResponseBody) -> Handler (Maybe ResponseBody)
の型シノニムです。
つまりnext
はHandler (Maybe ResponseBody)
です。
middleware1 <<< middleware2
のような形でミドルウェアを結合して組み合わせて一つのミドルウェアをつくります。
ResponseBody
はその名の通りレスポンスのボディですが、実態はReadable Streamとなっており、Readable StreamであればResponseBodyに変換可能です。また、String
からResponseBody
をつくることも可能です。
Maybe ResponseBody
となっていますが、Nothing
の場合だとボディ無しでレスポンスを返します。
当然next
を呼び出さなければ次のミドルウェアの処理には移りません。
Handler
モナドは、非同期な処理が中心のnodejsで、ミドルウェアがシーケンシャルに呼び出されることを保証するため、Aff
をベースにしています。また、nodejsはrequest streamとresponse streamを1リクエストで扱いますが、それらを1つにまとめたHttp
型をもっています。
早い話、具体的な型定義はnewtype Handler a = Handler (ReaderT Http Aff a)
となっています。
サーバーを作成
まずはこちらを見てください。
server :: Effect Server server = createServer middleware
さて、Middleware
はHandler (Maybe ResponseBody) -> Handler (Maybe ResponseBody)
でした。最後のnext
には何が渡されるのでしょうか?
createServer
の処理の中で、最後のnext
には、404を返すだけのHandler
が渡されます。
つまり、どのミドルウェアの処理にも引っかからないと404を返すようになっています。
サーバーを起動
最後はさっきのサーバーを起動するだけでサーバー出来上がりです! かなり簡単ですね。
main :: Effect Unit main = server >>= listen opts opts :: ListenOptions opts = { hostname: "127.0.0.1" , port: 3000 , backlog: Nothing }
詳しくはサンプルコードを見るとよりわかりやすいかもしれません。
既存のミドルウェア
こちらに現在8個のミドルウェアができています。 Bucketchainはあくまでインターフェースで、これに則ってミドルウェアとしてライブラリやフレームワークをつくるという世界観なので、より具体例を見るのであれば、こちらをご参照ください。
久しぶりにPureScriptのパッケージを書いたりしてた
追記 本記事のconveyor向けパッケージはメンテされていません。ご注意ください。
しばらくPureScriptから離れていたが、 サーバー用にいくつかほしくなった仕組みがあったため、久しぶりにPureScript書いた。
Update
- purescript-crypto
- basic認証用にタイミングセーフな比較処理を追加
- その他、軽微な変更
New
-
- nodeバックエンド向けのベーシック認証用のヘルパ
purescript-conveyor-basic-auth
- 拙作のpurescript-conveyor向けのbasic認証をやってくれるミドルウェア的なやつ
-
- 拙作のpurescript-conveyor向けのヘルスチェック用のエンドポイントを生やしてくれるミドルウェア的なやつ
とりあえずライブラリを書く時はまだしばらくbowerを使っておくことにした。
そんなにPureScript忘れてなかった。
久しぶりにPursuit見たらサーバー向けっぽいパッケージが増えてた。
Pursuitに最新版があがってないパッケージがあった。node系のやつだったかな。
PureScriptは0.12以降であんまり動きがないように見えてたけど、マイルストーンを見た感じ、1.0に向けて頑張ってるっぽいですね。
parcel-plugin-build-hooksつくった
parcelで任意のビルドイベント(例えば、ビルド完了時、ビルド開始時など)でcliを叩く、などをやりたい。 この場合、parcelの生APIを用いれば上記の目的は達成できるが、これはparcelの生APIを使ってビルドスクリプトを書くことを意味する。 なるべくビルドスクリプトは用意したくない。
こういった気持ちが生まれたため、プラグインにしときました。
使い方
package.json
に任意のイベント時に叩きたいコマンドを書く。
{ "name": "your-app", "main": "index.js", "dependencies": { "parcel-plugin-build-hooks": "^1.0.0" }, "parcel-plugin-build-hooks": { "buildStart": "// Do something", "bundled": "// Do something", "buildEnd": "// Do something", "buildError": "// Do something" } }
以上。
フリーランスになって1年経った
気づいたら、フリーランスになってから1年経っていました。 社会に出る前からフリーランスになる可能性は高そうだなと思っていたものの、踏ん切りがつかなかった自分が1年フリーランスできたことに対して、昔の自分は驚く気がします。
フリーランスになって
フリーランスになる前は以下について心配していました。
- 実力的な意味で自分がやっていけるのか
- お金の交渉をできるのか
- 仕事は取ってこれるのか
- 限られた時間で成果を出せるのか
1については、厳密には他人が決めることなので正確なことは言えません。ですが、話している感触としてはやっていけていると勝手に思っています。 他のフリーランスを見ていると、やっていきかたは人によって様々で、得意分野に特化して技術的な意味で役に立っているパターンもあれば、普通に機能開発を手伝っているパターンもあります。他人がしたことで社員が喜んでいるのを見たりして、「こういうのが喜ぶのか。喜ばれる目的に対してコスパがいいので記憶に残しておこう。」など、うまくやっている人を見て私も学んでいます。フリーランスの方は持ってるスキルも色々な意味で様々なんですが、スキルが原因でクビにされてるのは「今のところ」は見たことがないです。
2については、自分の場合は意外と出来ました。出来たというよりは、堂々とほしい金額を言っただけで払ってもらえている、といったところです。 わたしはこの1年のうちに3社経験していて(いずれも準委任です)、その中には金がある企業全然ない企業両方含まれています。 その前提で、すべて言い値で通りました。 ほしい金額を言うことがまず大切だなということを痛感しています。
3については、仕事を取りに行かずに済んでいる、という状況です。私はサラリーマンとして3社経験しています。そしてその3社で知り合った方々も色々なところへ散っていってます。私はサラリーマン時代に真面目に働いていたおかげかこの状況下なおかげか知り合い経由でお誘いが断続的に来ます。中にはお誘いを断っても定期的に移る気がないか確認してくる人もありがたいことに存在しています。 知り合いが重要というのは本当でした。
4については、自分の中ではまだ最大限ではないなと思っています。 まず業務時間については、サラリーマンで言うところの残業はそのまま損失なので、現在では、それを1mmもやらないようにし、業務してるとき以外は業務に関することをなるべく考えないように徹するようになりました。 また、今は2社から仕事を受けていますが、業務内容もまるっきし違いますし、1週間のうちに業務が切り替わるのもすごいコンテキストスイッチが発生します。 これらの前提、且つ、単位時間を1週間とみなした場合、サラリーマンとやっていたときと比べて単位時間あたりのパフォーマンスが落ちている気はしています。
不安はある
やはり不安なのは、いずれ誘ってくれる人がいなくなったらどうしようというのは常に頭にあります。 なので、1社1社、誠実に働かないといけないなとたまに考えたりします。 いつまでできるのかはわからないですが、どこかに入社したいなと思うまではフリーランスを続けようと思います。
Incremental lambda calculusとUIへの応用
はじめに
1ヶ月くらい前にpurescript-purviewというUIライブラリがリリースされました。 このライブラリは既存の仮想DOMに代わるような位置づけのライブラリとして実験的にリリースされたUIライブラリとされています。 何が実験的なのかというと、Incremental lambda calculusという概念に基づいて実装されているということでした。 一体何なんだこれは?と思ったので調べることにしました。
Incremental lambda calculus
ひとまずググってみたところ、こちらのサイトが出てきました。 上記のサイト内に論文のpdfへのリンクが貼ってありますが、「なるほどわからん」という感じです。 データベース技術を参考にした考え方っぽいことはわかりました。
さっぱりわからなかったときは実装を見たほうが早いということで、この考え方に基づいたライブラリを読むことにしました。 それがpurescript-incremental-functionsです。 purescript-purviewはこのライブラリをベースに実装されています。
コアとなる型クラスのドキュメントはこちらになります。
このソースコードから読み取れることは、「あるデータ型に対する操作を、変更増分で表現し、段階的に適用する」、というような考え方のようです。
例えば、こちらのライブラリで提供されているArrayの増分計算版であるIArray型の操作はInsertAt, DeleteAt, ModifyAtで表現するような形になっています。
次はこの考え方をどうUIに応用していくのか見ていきます。
UIへの応用
まず、前提として、purviewはこれまでの仮想DOMと同じように、状態(Modelという名前で扱われています)を受け取ってUIに変換します。そして、その状態への変更は、purescript-incremental-functionsを用いて定義された変更増分を用いて更新します。 また、purescript-incremental-functionsで提供されている型でJetというものがあります。こちらは計算対象の型と変更増分を両方もったデータ表現です。 purviewではあらゆるものをJetで表現します。purviewで提供されているUIの型もJetで記述していくような形になっています。
では、読んでいきましょう。 読むメインの場所はこのあたりになります。
まず、全体的な世界観としては、Jetで表現されたModelからJetで表現された仮想のUIのマッピングとなっていて、この関数にComponentという名前がついています。 ここで増分計算について考えます。Modelを変更したいので増分を与えるわけですが、先のComponentに現在のModelと、与えられた増分を用いたJetを渡します。 JetからJetのマッピングは増分もマッピングしてくれるため、Modelの増分から仮想のUIの増分が導きだせます。 これらのUI増分をpatchとして、現在の仮想のUIとそのpatchから生DOMに変更を与えていきます。 そして、次の計算に備えて、それぞれの増分から新しいModelと新しい仮想のUIを導き、referenceに保持し直して、1サイクル終了となります。
面白いところ
仮想DOMを読んだり実装したことがあればわかるのですが、現在の仮想DOMと新しい仮想DOMの差分計算の実装を必ず書くことになります。 よくあるkeyを与えたりするのは子要素の差分計算の効率のためです。 そして、現在の仮想DOMと新しい仮想DOMを直接比較しながら、あるいはその比較から差分表現を導き出して、生DOMに変更を適用していくような形になります。 この差分計算は通常かなり泥臭い実装になると思っています。
purviewの場合は、データの操作を増分で表現してしまい、その増分とユーザーが自ら定義した仮想のUI関数を利用して、UIの変更増分を直接導き出してしまいます。 このあたりの実装はとてもパズル的で面白く、非常にエレガントなやり方に見えました。(小並感)
一方で、増分適用できるデータ型を用意する必要がありますし、 データの変更を全て増分で与えなければいけないのも場合によっては面倒なケースがある気がしています。 例えば、IArrayを全て逆順にしたいときは全部DeleteAtしてから逆順にInsertAtするというような形でしょうか。あるいはAtomic型でくるんでまるごと置き換えるという形でしょうか。 いずれにせよ、増分の与え方は脳死状態ではできないケースがあるような気はしました。 なので、ヘルパー関数が充実していないと結構つらいケースがでてくるかもしれません。
さいごに
生DOMへの変更適用でこんな面白いやり方があるのかという発見がありました。 まだ出たばかりなのでバグがあるかもしれませんが、非常に勉強になるライブラリだと感じました。
日記
振込用紙
私はフリーランスでプログラマをやっていて、定期的に国民年金保険料の振込用紙が送られてきます。
封を開けるたびに「いつまで俺はフリーランスやってるだろう?」と無駄に考えます。
このように考える要因はもしかしたらどこかでフリーランスをやめるってことも想定しているからだと思います。
なので、基本的には自分の求める条件に合う場所が万が一あれば就職したいのかもしれません。(が、おそらくありません。)
怠けるループ
たまに何もしたくなくなる時期が襲ってきます。
そのような時期に突入したときになるべく早く脱出する方法を考えていました。
洗い物や掃除(特に水回りなど)は、終わったあとがピカピカで気持ちがよく、不思議と活動意欲が湧いてくるので、地味だけど効果がありそうです。前頭葉に刺激うんぬんみたいな理屈を見たことがあるようなないような。まぁそのあたりのことはどうでもいいです。
この記事
この記事はなんの意味もありません。 作業で行き詰まったところ、脳みそを休ませるつもりで書いてみました。
おっさんくさいこと言ってたのかもしれないと思って勝手に反省していた
はじめに
特にきっかけがあるわけでもないが、自分が技術的選択においておじさんくさいことを言っていたのではないか? ということをつらつらと書くという内容です。 具体的には、「実質的にリターンがあることに対してコストがある事柄について、これは多くの人間にとってリターンにコストが見合わないんじゃないか」とか言っちゃうことをおっさんくさかったのでは、と反省する記事です。
この話の前提となる私の思考癖
技術選定や設計など、技術的選択を行う時に、以下のことを私は考えています。
- 現在の要求される仕様に技術・設計がマッチしているか、オーバーではないか、貧弱ではないか?
- この先、どのような機能の改修が入るか、どういう進化の道を辿るか?(そんなに遠くを考え過ぎないようにはするが)
- それは今考えるべきことか?あとから問題起こった時に考えるということでもいいのではないか?
1について
自明な話なので、説明は割愛。
2について
「機能の改修や進化」は、サービスの性質あるいは人間から生まれるものですよね。 これらは、サービスの性質や、組織や発言権の強い人間の思考癖、あるいは日常の中に出る発言によって、 ある程度予測可能な部分があると思っています。 そして、これらの予測範囲はサーバーからGUIに及ぶが、今後予測される複雑性に対する拡張性や技術的難易度の上がり方などに影響すると考えています。 また、組織や人間の癖がより色濃く出やすいのがGUIであると考えていて、みんなやりたいことがあって、だいたい頻繁に手が入るので、 なんも考えてないとGUIは比較的困ったことになりやすいと考えています。 よって、この項目についても考えることにしています。
3について
あまり早い段階で間違った最適化をしないように気をつけているというだけです。 1しか考えてなくても間違った最適化をするかもしれないし、2の考えに寄りすぎると、間違った最適化をすると考えているためです。
「コストにリターンが見合わない」のコストとリターンとは?
上記の前提において、コストとはなんなのか、と考えると、 例えば、学習コスト(技術選定の場合でも、アプリケーションコードの実装についても)、 あるいは、単純に実装コスト(これは単純に実装にかかる工数とする)、ということになると思う。
リターンは、例えば、より複雑な要求に耐えうる、とか、はやくなるとか、 まぁなんでもありそうですね。
私の思考癖で考えることは、選択の裏付けになっていない
上述した思考癖のうち、1は「現在」という確度の高い情報に基づいて考えることになるので、1で考えたことは裏付けとして使ってもいいと考えている。
しかし、2についてはどうか。 まず、そもそも、入社したてで機能改修の傾向がつかめていなかったり、極めて理屈のない思いつきでものを言う強権者もいたりするし、 あるいは、あらたな強権者がチームに入ってくるなどもあり、 私の考える「システムの進化予測」はかなり不確実性が高いということになる。 そして、2の項目における技術的判断のリターンは、この不確実な「システムの進化予測」から影響を受ける。 つまり、この、リターン予測も不確実ということになる。
また、コストについてだが、学習コストや実装コストは、人によって大きく変わることがある。 これは、どういう経験をしてきたかにもよるし、メンタルな部分もあると思っている。 これらについて、私が他人のことを考えて「きっと大変だろう」とか思っても、 結局わからないことを勝手に決めつけて考えているに過ぎない。
つまり、「コストに見合うリターン」というのを、コスト面についてもリターン面についても、私は雰囲気で考えているということになる。 また、結果的に思考癖の3も雰囲気とか感覚で考えていることになる。
(とはいえ、これらの事柄について、全く考えないべきだというのもまずい気がするので、一応考えることには変わりない)
雰囲気で考えているのにもかかわらず
あまり理のないことを言っているのにもかかわらず、なんでこんなことを言っちゃうのか?
これは、私が「コストにリターンが見合わないんじゃないか」と言う時に「他人のことを考えて大人っぽいことを言っていてえらい」という感覚がどこかあって悪いことをしているという感覚がなかったからなのではないかと思っている。
そもそも、理があろうがなかろうが、他人のことを考えたからといって、それがいいことかどうかは他人が決めることなのでわからない。 他人のことを考えたから良いことをしている、というのはかなり傲慢な心理に思える。
私はこれを「おっさんくさかったのではないか?」と思ってこんなポエムを書くに至った。
結局どうすればいいのか
このことについて、今後、私はどういう態度をとるべきかというと、「だれでも気軽に脊髄反射で反論しやすい空気感の言葉を使い、私の考えてることに対して、意見をもらうようにする」ということくらいしか思いつかない。
結局これで誰からも意見がもらえないということもあるだろうし、意見をもらったからと言って、最高の選択ができるかというとそれは最終的には未来にしかわからない。そのまた未来では、クソだったということもあるだろう。これは時間経過で評価が変わっていく。
だが、「コストにリターンが見合わないんじゃないか」と言いたくなった時に、「俺はおっさん臭くなってるかもしれないから、ちょっと色々聞いてみるか」という気持ちとか表情を出力したほうが誠実な振る舞いだろう。
「あいつめっちゃ加齢臭するよな!!」とか言われるのも悲しいですし...