あどけない話

Internet technologies

2023年にHaskell関連で知ってよかったこと

これはHaskell Advent Calendar 2023の19番目の記事です。

フォーマッター

以前、フォーマッターをいくつか試しましたが、どれもイマイチでした。しかし、fourmoluはいけてます。fourmoluは、Ormoluのフォークで、Ormoluが偉大なのでしょう。両方試しましたが、僕はformoluに決めました。

Hackageに上がっているので好きな方法でインストールしてください。

% cabal install fourmolu

formoluにHaskellのプログラムを渡すと、整形したプログラムを出力してくれます。ファイルの内容を直接書き換えたいときは、-iオプションを渡します。エディタやIDEと連動できますが、お試しでプロジェクト全体を整形するには、以下のようにするといいでしょう。

% find . -name "*.hs" | xargs fourmolu -i

整形が気に入らない部分は、{- FOURMOLU_DISABLE -}{- FOURMOLU_ENABLE -}で囲んで、手で修正してください。

CPPで #ifdef している部分は、書き方によって、整形できたり、できなかったりします(ファイル全体の整形を諦める)。関数の一部ではなく、冗長になるかもしれませんが関数の全体を#ifdefで囲むように変形すると、きっとうまく整形できるでしょう。

整形の方法をfourmolu.yamlでカスタマイズできます。fourmoluは、起動時にルートディレクトリに向かって、このファイルを探します。設定の各種項目を試せるWebサイトがあるので、そこで遊んでみましょう。僕が使っている設定ファイルは、ここを見てください。

コールグラフ

いつの間にか巨大になってしまったモジュールを分割するとき、コールグラフがあれば、呼び出し関係の薄い部分で分割できると判断できます。認知度が低いのですが、イケてるコールグラフ作成ツールが、calligraphyです。

% cabal install calligraphy

GHCが生成する IDE Infoからコールグラフを作成します。ほーら、イケてる気がするでしょう?使い方は、こんな感じです。

% cabal build --ghc-options=-fwrite-ide-info
% calligraphy Network.TLS.Handshake.Client --output-png out.png

この場合のout.pngは、こんな感じです。

オプションがたくさんあるので、いろいろ試してみてください。IDE Infoを使うので、calligraphyをビルドしたGHCのバージョンと、プロジェクトに使う GHC のバージョンは一致させる必要があります。

リアライザー

Haskelldataシリアライズするには、歴史的に binary や cerial が使われてきました。これらのライブラリでは、定義したdataに対するシリアライザーやデシリアライザーを手書きする必要があります。

最近のライブラリでは、シリアライザーやデシリアライザーを自動生成できます。その一つであるserialiseの使い方を紹介します。

% cabal install serialise

基本型は、デフォルトで扱えるようになっています。

% ghci    
ghci> import Codec.Serialise
ghci> serialise (1 :: Int)
"\SOH"

シリアライズできました。では、デシリアライズを試してみましょう。

ghci> deserialise $ serialise (1 :: Int)
*** Exception: DeserialiseFailure 0 "expected null"

作成されたバイナリには、型情報がないんですね。(出力が1バイトの時点で気づけって?) 型を補ってみましょう。

ghci> deserialise $ serialise (1 :: Int) :: Int
1

ヤッホー! では、自前の型を作ってみます。シリアライザを自動生成するには、DeriveGeneric 言語拡張が必要です。

ghci> :set -XDeriveGeneric 
ghci> import GHC.Generics
ghci> data Tree = Leaf | Node Int Tree Tree deriving (Generic)

そして、以下の魔法を唱えると、シリアライザ&デシリアライザが自動生成されます。

ghci> instance Serialise Tree
ghci> serialise Leaf
"\129\NUL"
ghci> serialise $ Node 1 Leaf Leaf
"\132\SOH\SOH\129\NUL\129\NUL"

やったね!

軽量スレッドのスタック

軽量スレッドが生成されたとき、スタックの最初の大きさは 1KiB です。スタックが消費されたら自動的に伸びますが、伸びる大きさは 32KiBです。つまり、1KiBの次は33KiBになってしいます。なので、たくさん軽量スレッドを使うと、使用するメモリの大半をスタックが占めるようになります。

実際は、スタックの大きさは2KiBでも事足りているかもしれません。伸びたスタックが縮むことはありません。スタックの伸びる大きさを制御する RTS オプションが -kcです。指定できる最小値は、2KiB(-kc2k)のようです。

この事実を知ってから、僕が作っているサーバの cabal ファイルには、以下の行を入れるようになりました。(他の RTS オプションの意味は、各自で調べてください。)

 ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-qn1 -A32m -kc2k"