前職での Haskell 製ライブラリが幾つか OSS 化されたので紹介する
はじめに
前職(DeepFlow)で開発した Haskell 用のライブラリ群がOSS化された(かつての同僚のみなさま、ありがとうございます🙏)ので、その内で私が主に開発を担当したものについて、遅ればせながら幾つか紹介したいと思います。
いずれも実務でバリバリ使っていたライブラリなので、もしよかったら使ってみてください。
謝辞
お忙しい中OSS公開のために動いてくださった、前職の同僚の皆さんに感謝します。ありがとうございました🙏
guardian
: メガレポの依存関係強制ツール
Haskell製メガレポのパッケージ間の依存関係を細かく分類し、依存性の逆転を強制するためのツールです。前職のプロダクトは小さな変更をするたびに数時間リビルドが走ってたところ、これによって小さな変更であれば十分もかからずにビルドできるようになりました。
こちらは既に二年くらい前に解説記事を書きましたので、是非読んでみてください。
elgenerics
: 依存型プログラミングと拡張可能レコードのライブラリ
elgenerics は、GHC の型レベルプログラミングと拡張可能レコードの機能群を提供する総合的なライブラリで、かれこれ5年くらいはコードベースの中心で使われつづけていたものです。
以下の機能を提供しています:
- シングルトンによる依存型プログラミングライブラリ
-
singletons
系パッケージと概ね同じですが、Known
という型クラスを提供しています。 - 特に型レベル自然数、リスト、赤黒木などを提供しており、これら用の便利関数などを提供しています
-
- 強力な型検査器プラグイン
- 型レベルリスト、型レベル赤黒木などの型レベル制約を自動的に解決してくれる型検査器プラグインを提供しています。
- たとえば
Member k ks
およびAll c ks
からc k
を推論してくれたり、Member k ks
からMember (f k) (Map f ks)
を推論してくれたりします。 -
Deferrable
の解決などもしてくれるやつもいます - 型族の定義を自動的に展開してコンパイル時間を節約してくれるプラグインもある
- 拡張可能レコード
- 拡張可能レコードといえば、fumievalさんの
extensible
やvinyl
などが有名ですが、elgenerics
では任意の(型レベルOrd
を実装している)カインドの型をフィールドに使うことができます。 - ラベルに依存するものとしないもの、両方用意しています。
- Higher-Kinded Datatype の考え方に基づいた、様々な関数を提供しています。
- 拡張可能レコードを少しずつつくっていくためのビルダなども用意
- Heterogeneous List も提供されています
- 双対である dependent sum もあります(多分これは
dependent-sum
を使ったほうがいい)
- 拡張可能レコードといえば、fumievalさんの
実戦に耐える割と便利なものができたと思っているので、もしかしたらそのうち記事を書くかもしれません(書かないかもしれません)。
deriving-modifiers
: 様々なインスタンスを導出するためのユーティリティライブラリ
構造的な coerce をやったり、あるいは DerivingVia で使うための色々な newtype wrapper が用意されています。
ToJSON
, FromJSON
をするときに特定のフィールドだけ見るようにしたり、レコードのフィールドを別の名前のフィールドとして扱ったりできます。
あとは Generic な表現が同じ型同士の Derive をするための修飾子などもあります。
streaming-extras
: streaming
エコシステムでのストリーム処理用ライブラリ群
前職では、ストリーム処理には streaming
ライブラリのエコシステムを使っていました。
他にも conduit
とか pipes
とか streamly
とかストリーミング用のライブラリには幾つかメジャーなものがありますが、以下の理由で streaming をよく使っていました:
- インタフェースが直感的
- リストなどを変換していくように、
Stream (Of a)
型の値を直接変換していくので直感的です -
conduit
のように複数の異なる型があるわけではないので、直感的
- リストなどを変換していくように、
- ドキュメントがわかりやすい
-
pipes
やstreamly
は強力なんだろうけどちょっとどうつかっていいのかがわからず……。
-
-
conduit
よりパフォーマンスがいい- 常にかというとちゃんと測定したことはないですが、以前個人的に書いたプログラムでは streaming の方が conduit より速い場合がありました。
- 任意のモナドに対応している
- それなりにライブラリが充実している
streaming
はまあまあ充実しているとはいえ、微妙に足りないものもあったので、streaming-extras
では以下のパッケージを提供しています:
-
streaming-SHA
:SHA
パッケージへのバインディング。ストリームに対するハッシュ計算ができる。 -
streaming-archive
:Zip
,Tar
,LZMA
(.xz
) のストリーム処理。 -
streaming-http
:http-client
でストリーム処理をするレイヤ。 -
streaming-s3
: Amazon S3 互換のオブジェクトストレージに対して V4 署名をしてストリーミングしながらリクエストをするためのライブラリ。 -
streaming-servant-orphans
: Servant でstreaming
を使うためのブリッジ。
一部は streaming-utils
などと機能が被っていますが、個別の機能だけがほしかったり、微妙に使い方がこちらの想定とズレているものがあったりした場合、ここでも似たようなパッケージを提供しています。
streaming
はストリームの変換として直に書ける嬉しさがあり、その単純さゆえに効率もそれなりに出ますが、一方で消費済のストリームを再利用してはいけないという制限があります。
だいたいの Stream は &
や >>=
とかで使って直接消費しますし、変換子は関数合成で書くというスタイルが(暗黙の内に)推奨されているので、二度漬け禁止に引っかかることは(慣れていれば)あんまりないですが、不安ですよね。
ということで、Linear Haskell の標準ライブラリ linear-base
では、線型型を使って一回しか消費しないことが型レベルで強制される Stream
型が提供されています。
多重度が違うので streaming
エコシステムで提供されているものとは違う型になりますが、資源管理にかなり気をつかっている Linear Haskell 界隈の人も streaming
と同じ定式化を使っている、というのはセールスポイントになるでしょう。
effectful-extras
: effectful
イフェクトシステムのユーティリティライブラリ群
ちょっと前まで、Haskellでアプリケーションを実装する際には RIO パターンを使うのが常套手段でしたが、この RIO パターンを実装に使い、その上にイフェクトシステムを実装した effectful というエコシステムがあります:
るじゃんどる氏が見付けてきてくれ、便利そうじゃん!というのでここ数年はアプリケーションは effectful を使って記述していました。
この中で足りないなあと思った機能を補うのが effectful-extras
レポジトリで、以下のものがあります:
-
path-tagged-effectful
: 私が個人的に開発している、path-tagged
を effectful で使うためのもの。-
path-tagged
はpath
ライブラリの更に型がついている版で、各パスに「これは設定ファイルへのパスです」「これは設定ディレクトリへのパスです」といったアプリケーション側の意味論に基づいたタグをつけるためのライブラリです。
-
-
Glob-effectful
:FileSystem
ハンドラ下で、Glob パターンが使えるようにするもの。個人的に開発していた path-tagged パッケージとの併用を前提としています。 -
beam-sqlite-effectful
: SQLite データベースを Beam 越しに使うためのブリッジ。 -
effectful-expiry
: 期限つきのリソースを Lazy に更新しながら使うためのライブラリ。 -
effectful-lens
: Static Reader や State などに対して、通常のlens
のview
やuse
に当るレンズを提供するもの。 -
effectful-github
: GitHub API を呼び出すためのライブラリ。 -
log-effectful-extra
:effectful
とロギングライブラリlog
のブリッジであるlog-effectful
の、一世代前のパッケージに足りていなかった機能を集めたもの。最新の環境下では使えないです。log-effectful
がPVPを守ってないので気を付けてください……。 -
lukko-effectful
: ファイルロックライブラリlukko
をFileSystem
ハンドラ下で使うためのもの。 -
module-effectful
: Environment Module をパーズして環境変数などの情報を取得したり、process に渡したりするためのもの。 -
random-effectful
:random
パッケージ経由で乱数生成を行うRandom
ハンドラを提供するパッケージ。 -
s3-effectful
: Amazon S3互換 API にリクエストを投げるための効果S3
を提供するパッケージ。必要なリクエストしかサポートしてないです。 -
shake-effectful
:Shake
ビルドツールのビルドルールを effectful 内で実行するためのパッケージ。 -
streaming-effectful
:streaming
関連の機能(主にFile I/O)を effectful の中で使うためのもの。 -
time-effectful
:getCurrentTime
などを呼び出すための、Clock
ハンドラを提供するパッケージ。time
のラッパー。 -
typed-process-effectful-effectful
:type-process-effectful
に足りない機能を提供。log-effectful
との interpo や出力のデリゲートなど。
イフェクトシステムの用途を考えると本当はもっとモックを前提とした設計にすべきなんですが、結構わりと雑に直接裏で IO を呼ぶ形になっています。
長期的にはSQLite まわりなどはモックをしやすいようにしていきたいですね……。
その他のレポジトリ
HsYAML-pretty
: HsYAML のカスタマイズ可能なプリティプリンタ
YAMLで設定可能な自動化ツールに、デフォルトの設定ファイルの初期設定を生成したくなることがあります。
このようなとき、各フィールドのドキュメントを書いたコメントを指定したり、フィールドの出力順を指定できると便利ですが、現行の yaml
パッケージや HsYAML
パッケージには、このような機能はありません。
そこで、HsYAML-pretty
ではこうした機能を autodocodec に近いようなインタフェースで提供し、プリティプリント・パーズができるようになっています。
まだまだ機能としては完全ではないですが、とりあえず最小限必要な機能はある、という状況です。
hs-bash-script
: 簡単な Bash スクリプト合成用ライブラリ
適切にエスケープをしつつ、Bash スクリプトを合成するためのライブラリです。
実体としては、neat-interpolation
と shell-escape
の上の薄いラッパーになっています。前職では、スパコンのジョブスクリプトを生成するのに利用していました。
cmark-combinators
: Pandoc-style で CommonMark を合成するためのコンビネータライブラリ
単純な Markdown を描画したいだけだと、Pandoc は依存関係として重いです。そこで、CommonMark の範囲であれば cmark
パッケージを利用することを良く検討します。とはいえ、cmark
は libcmark
の直接的なラッパーなので、インラインvsブロックなどの区別もなく、構文木もわかりづらいです。cmark-combinators
は cmark
の型をラップしてPandocのような形で commonmark を合成するコンビネータを提供しています。
hmsh
: HKD を使った強い型つきメッシュ生成用 Gmsh APIバインディング
Gmsh というメッシュングツールの API の Haskell バインディングです。
Gmsh では三次元メッシュを、より低次元の要素を何回も「押し出し」て定義することが多いですが、この押し出し後の値の対応を取るのが面倒くさいという問題があります。
このライブラリでは HKD を使うことで、押し出した後の反対側の要素と、新たに生まれた高次元要素を同じレコードの同じフィールドに入れた状態で区別できるようにしています。
これを便利がって貰えるのは多分前職くらいだと思いますが、HKDを使った「型が変わり得るレコードの変換」のインタフェースの設計には参考になるかもしれません。
最後に
という訳で前職でのOSSの紹介でした。これらは私ひとりで開発したものでは決してなく、日々の開発の中で自然と必要になり、前職の同僚の皆さんと一緒に使って作っていったものです。今後もどんどん使いたいと思いますので、良さそうなものがあったらぜひ使ってフィードバックを頂ければ幸いです。
Discussion