💿

前職での Haskell 製ライブラリが幾つか OSS 化されたので紹介する

2025/01/15に公開

はじめに

前職(DeepFlow)で開発した Haskell 用のライブラリ群がOSS化された(かつての同僚のみなさま、ありがとうございます🙏)ので、その内で私が主に開発を担当したものについて、遅ればせながら幾つか紹介したいと思います。
いずれも実務でバリバリ使っていたライブラリなので、もしよかったら使ってみてください。

謝辞

お忙しい中OSS公開のために動いてくださった、前職の同僚の皆さんに感謝します。ありがとうございました🙏

guardian: メガレポの依存関係強制ツール

https://github.com/deepflowinc-oss/guardian

Haskell製メガレポのパッケージ間の依存関係を細かく分類し、依存性の逆転を強制するためのツールです。前職のプロダクトは小さな変更をするたびに数時間リビルドが走ってたところ、これによって小さな変更であれば十分もかからずにビルドできるようになりました。
こちらは既に二年くらい前に解説記事を書きましたので、是非読んでみてください。

elgenerics: 依存型プログラミングと拡張可能レコードのライブラリ

https://github.com/deepflowinc-oss/elgenerics

elgenerics は、GHC の型レベルプログラミングと拡張可能レコードの機能群を提供する総合的なライブラリで、かれこれ5年くらいはコードベースの中心で使われつづけていたものです。
以下の機能を提供しています:

  1. シングルトンによる依存型プログラミングライブラリ
    • singletons 系パッケージと概ね同じですが、Known という型クラスを提供しています。
    • 特に型レベル自然数、リスト、赤黒木などを提供しており、これら用の便利関数などを提供しています
  2. 強力な型検査器プラグイン
  3. 拡張可能レコード
    • 拡張可能レコードといえば、fumievalさんのextensiblevinylなどが有名ですが、 elgenerics では任意の(型レベル Ord を実装している)カインドの型をフィールドに使うことができます。
    • ラベルに依存するものとしないもの、両方用意しています。
    • Higher-Kinded Datatype の考え方に基づいた、様々な関数を提供しています。
    • 拡張可能レコードを少しずつつくっていくためのビルダなども用意
    • Heterogeneous List も提供されています
    • 双対である dependent sum もあります(多分これは dependent-sum を使ったほうがいい)

実戦に耐える割と便利なものができたと思っているので、もしかしたらそのうち記事を書くかもしれません(書かないかもしれません)。

deriving-modifiers: 様々なインスタンスを導出するためのユーティリティライブラリ

構造的な coerce をやったり、あるいは DerivingVia で使うための色々な newtype wrapper が用意されています。
ToJSON, FromJSON をするときに特定のフィールドだけ見るようにしたり、レコードのフィールドを別の名前のフィールドとして扱ったりできます。
あとは Generic な表現が同じ型同士の Derive をするための修飾子などもあります。

streaming-extras: streaming エコシステムでのストリーム処理用ライブラリ群

https://github.com/deepflowinc-oss/streaming-extras

前職では、ストリーム処理には streaming ライブラリのエコシステムを使っていました。

https://hackage.haskell.org/package/streaming

他にも conduit とか pipes とか streamly とかストリーミング用のライブラリには幾つかメジャーなものがありますが、以下の理由で streaming をよく使っていました:

  1. インタフェースが直感的
    • リストなどを変換していくように、Stream (Of a) 型の値を直接変換していくので直感的です
    • conduit のように複数の異なる型があるわけではないので、直感的
  2. ドキュメントがわかりやすい
    • pipesstreamly は強力なんだろうけどちょっとどうつかっていいのかがわからず……。
  3. conduit よりパフォーマンスがいい
    • 常にかというとちゃんと測定したことはないですが、以前個人的に書いたプログラムでは streaming の方が conduit より速い場合がありました。
  4. 任意のモナドに対応している
  5. それなりにライブラリが充実している

streaming はまあまあ充実しているとはいえ、微妙に足りないものもあったので、streaming-extras では以下のパッケージを提供しています:

  • streaming-SHA: SHA パッケージへのバインディング。ストリームに対するハッシュ計算ができる。
  • streaming-archive: Zip, Tar, LZMA (.xz) のストリーム処理。
  • streaming-http: http-client でストリーム処理をするレイヤ。
  • streaming-s3: Amazon S3 互換のオブジェクトストレージに対して V4 署名をしてストリーミングしながらリクエストをするためのライブラリ。
  • streaming-servant-orphansServantstreaming を使うためのブリッジ。

一部は streaming-utils などと機能が被っていますが、個別の機能だけがほしかったり、微妙に使い方がこちらの想定とズレているものがあったりした場合、ここでも似たようなパッケージを提供しています。

streaming はストリームの変換として直に書ける嬉しさがあり、その単純さゆえに効率もそれなりに出ますが、一方で消費済のストリームを再利用してはいけないという制限があります。
だいたいの Stream は &>>= とかで使って直接消費しますし、変換子は関数合成で書くというスタイルが(暗黙の内に)推奨されているので、二度漬け禁止に引っかかることは(慣れていれば)あんまりないですが、不安ですよね。
ということで、Linear Haskell の標準ライブラリ linear-base では、線型型を使って一回しか消費しないことが型レベルで強制される Streamが提供されています。
多重度が違うので streaming エコシステムで提供されているものとは違う型になりますが、資源管理にかなり気をつかっている Linear Haskell 界隈の人も streaming と同じ定式化を使っている、というのはセールスポイントになるでしょう。

effectful-extras: effectful イフェクトシステムのユーティリティライブラリ群

https://github.com/deepflowinc-oss/effectful-extras

ちょっと前まで、Haskellでアプリケーションを実装する際には RIO パターンを使うのが常套手段でしたが、この RIO パターンを実装に使い、その上にイフェクトシステムを実装した effectful というエコシステムがあります:

https://haskell-effectful.github.io

るじゃんどる氏が見付けてきてくれ、便利そうじゃん!というのでここ数年はアプリケーションは effectful を使って記述していました。
この中で足りないなあと思った機能を補うのが effectful-extras レポジトリで、以下のものがあります:

  • path-tagged-effectful: 私が個人的に開発している、path-tagged を effectful で使うためのもの。
    • path-taggedpath ライブラリの更に型がついている版で、各パスに「これは設定ファイルへのパスです」「これは設定ディレクトリへのパスです」といったアプリケーション側の意味論に基づいたタグをつけるためのライブラリです。

  • Glob-effectful: FileSystem ハンドラ下で、Glob パターンが使えるようにするもの。個人的に開発していた path-tagged パッケージとの併用を前提としています。
  • beam-sqlite-effectful: SQLite データベースを Beam 越しに使うためのブリッジ。
  • effectful-expiry: 期限つきのリソースを Lazy に更新しながら使うためのライブラリ。
  • effectful-lens: Static Reader や State などに対して、通常の lensviewuse に当るレンズを提供するもの。
  • effectful-github: GitHub API を呼び出すためのライブラリ。
  • log-effectful-extra: effectful とロギングライブラリ log のブリッジである log-effectfulの、一世代前のパッケージに足りていなかった機能を集めたもの。最新の環境下では使えないです。log-effectful がPVPを守ってないので気を付けてください……。
  • lukko-effectful: ファイルロックライブラリ lukkoFileSystem ハンドラ下で使うためのもの。
  • 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 のカスタマイズ可能なプリティプリンタ

https://github.com/deepflowinc-oss/HsYAML-pretty

YAMLで設定可能な自動化ツールに、デフォルトの設定ファイルの初期設定を生成したくなることがあります。
このようなとき、各フィールドのドキュメントを書いたコメントを指定したり、フィールドの出力順を指定できると便利ですが、現行の yaml パッケージや HsYAML パッケージには、このような機能はありません。
そこで、HsYAML-pretty ではこうした機能を autodocodec に近いようなインタフェースで提供し、プリティプリント・パーズができるようになっています。
まだまだ機能としては完全ではないですが、とりあえず最小限必要な機能はある、という状況です。

hs-bash-script: 簡単な Bash スクリプト合成用ライブラリ

https://github.com/deepflowinc-oss/hs-bash-script

適切にエスケープをしつつ、Bash スクリプトを合成するためのライブラリです。
実体としては、neat-interpolationshell-escape の上の薄いラッパーになっています。前職では、スパコンのジョブスクリプトを生成するのに利用していました。

cmark-combinators: Pandoc-style で CommonMark を合成するためのコンビネータライブラリ

https://github.com/deepflowinc-oss/cmark-combinators

単純な Markdown を描画したいだけだと、Pandoc は依存関係として重いです。そこで、CommonMark の範囲であれば cmark パッケージを利用することを良く検討します。とはいえ、cmarklibcmark の直接的なラッパーなので、インラインvsブロックなどの区別もなく、構文木もわかりづらいです。cmark-combinatorscmark の型をラップしてPandocのような形で commonmark を合成するコンビネータを提供しています。

hmsh: HKD を使った強い型つきメッシュ生成用 Gmsh APIバインディング

https://github.com/deepflowinc-oss/hmsh

Gmsh というメッシュングツールの API の Haskell バインディングです。

https://gmsh.info

Gmsh では三次元メッシュを、より低次元の要素を何回も「押し出し」て定義することが多いですが、この押し出し後の値の対応を取るのが面倒くさいという問題があります。
このライブラリでは HKD を使うことで、押し出した後の反対側の要素と、新たに生まれた高次元要素を同じレコードの同じフィールドに入れた状態で区別できるようにしています。
これを便利がって貰えるのは多分前職くらいだと思いますが、HKDを使った「型が変わり得るレコードの変換」のインタフェースの設計には参考になるかもしれません。

最後に

という訳で前職でのOSSの紹介でした。これらは私ひとりで開発したものでは決してなく、日々の開発の中で自然と必要になり、前職の同僚の皆さんと一緒に使って作っていったものです。今後もどんどん使いたいと思いますので、良さそうなものがあったらぜひ使ってフィードバックを頂ければ幸いです。

Discussion