久々にHaskellでプログラムを書いて学んだこと (3): コーディングスタイルなど
コードフォーマット
最近はGo言語を書く機会もあり、そこでgo fmtいいなと思ったので、Haskellでもコードフォーマッタを導入することにした。
とりあえずstylish-haskellを使うことにした。Emacsで使うので、設定ファイルは自分のEmacs設定レポジトリに入れている。
やはりimportリストを勝手に整理してくれるのはありがたい。自分は基本的にnon-qualified wildcard importを使わず、各シンボルをexplicit importするタイプなのでimport listがすぐにぐちゃぐちゃになる。
とりあえずstylish-haskellにしてみたが、ormoluなんかでもよかったかもしれない。
Custom preludeっぽいモジュール
前述のように、自分は基本的にwildcard importはしないようにしている。つまり、基本的にimportは
import Hoge (a,b,c) import qualified Foo as F
上記の2パターンのどちらかになる。wildcard importをやりだすとシンボルの出自が分からなくなり、書いた本人ですらコードが理解しにくくなるからだ。まあ、IDEをちゃんと使えばそんな心配いらなくなるのかもしれないが。
とはいえ、さすがにモジュールの数が多くなってくるとimport listを書くだけで一苦労になってくる。特に、どんなコードでも必ず使うようなシンボルを繰り返しimportに書くのはさすがに気が滅入ってくる。
ということで、そういったよく使うシンボルについてはまとめて一つのモジュールでimportしておいて、プロジェクトの他のコードはそのモジュールをimportするようにした。例えば、
module MyApp.Base ( module X ) where import Control.Applicative as X (empty) import Control.Exception.Safe as X (MonadCatch, MonadThrow, catch, handle, throw, throwIO, throwString) import Control.Monad as X (forM_, forever, void, when) import Control.Monad.Logger as X (MonadLogger (..)) import Control.Monad.Reader as X (MonadReader (..), ReaderT (..), runReaderT) import Control.Monad.Trans as X (MonadIO (..), MonadTrans (..)) import Control.Monad.Trans.Resource as X (MonadUnliftIO, MonadResource) import Crypto.Random as X (MonadRandom (..)) import Data.Aeson as X (FromJSON (..), ToJSON (..)) import Data.ByteString as X (ByteString) import Data.HashMap.Strict as X (HashMap) import Data.Monoid as X (Monoid (..)) import Data.Proxy as X (Proxy (..)) import Data.Semigroup as X (Semigroup (..)) import Data.Text as X (Text) import Data.Traversable as X (Traversable (..)) import GHC.Exts as X (IsList (..)) import GHC.Generics as X (Generic) import GHC.Records as X (HasField (..))
といったモジュールを書いておく。このモジュールではre-exportのテクニックを使う。
利用する側のモジュールは単にMyApp.Base
から普通にシンボルをimportする。
module MyApp.Foo import MyApp.Base (MonadThrow, MonadIO, MonadReader, FromJSON, ToJSON, Generic)
依然としてexplicit importをするのでimport listは長くなるが、「あのtypeclassってどのモジュールにあったっけ?」と悩まずにガンガン書ける分、気分的には楽になる。
このやり方はrioやclassy-preludeといったcustom preludeのアプローチに似ているが、個人的にはPreludeを置き換えるほどのこともないのではと考えている。うかつにPreludeを置き換えると、やはりシンボルの出自がよく分からなくなるのが怖い。
MyApp.Base
相当のモジュールはプロジェクトの特質にあわせてプロジェクトごとに育てていけばいいように思う(チームで開発をするとなるともっと統制が必要かもしれないが)。何をBaseに突っ込むかは悩みどころだが、explicit importをする前提なら大抵のシンボルを放り込んでもそんなに問題ないだろう。ただ、将来的に分離する可能性がある依存パッケージのシンボルはあまりホイホイ突っ込むべきではないだろう。