Haskellでimport文をソートするプラグイン vim-haskell-sort-import を作りました

Go言語には、gofmtというコードフォーマッターがあります。 標準ライブラリーで備えており、このフォーマッターをかけることを半ば強制することにより、Goで書かれたコードはどれも統一的なスタイルをしているように見えます。 gofmtには様々な機能がありますが、特にimportしているモジュールをソートしてくれるのが便利だなと思います。

フォーマッターがimportのモジュールをソートしてくれるのはとても便利です。 第一に、どのファイルにおいてもあるモジュールはだいたい同じ場所にあります。 例えばbufioモジュールは上のほうで、osモジュールは下のほうがといった感じです。 第二に、モジュールを追加するときにどこに追加すればよいかということに気を回さなくてすみます。 何か新しいモジュールを追加しようという時に、どこに追加するかについて私たちはかなり気を使っているように思います。 もちろん、import文をひっくり返しても言語の仕様上において意味は変わらないということが大前提にあります。

Haskellを書いていても、モジュールをどの位置にimportするか悩みます。 適当にimportを書いていくとファイルによってマチマチになりがちです。 そこでHaskellでもimport文を常にソートするようにしようと思いたち、Vimプラグインを作ってみました。 github.com このプラグインは一つのコマンドを提供します。 その名も、 HaskellSortImport です。 そのままですね。

例えば、次のようなコードがあったとします。

import Data.Monoid
import qualified Data.HashMap.Lazy as HM
import Control.Monad (forM_, when)
import Data.Hashable
import Data.Maybe (mapMaybe)
import Data.Char (isAlpha)
import Data.List (foldl')
import System.Info (os)
import System.IO (hFlush, stdout)
import System.Exit (ExitCode(..))

main :: IO ()
main = do
  ...

import文のモジュールはソートされていませんね。 ここで、次のコマンドを叩きます。

:HaskellSortImport

すると、次のようになります。

import Control.Monad (forM_, when)
import Data.Char (isAlpha)
import Data.Hashable
import qualified Data.HashMap.Lazy as HM
import Data.List (foldl')
import Data.Maybe (mapMaybe)
import Data.Monoid
import System.Exit (ExitCode(..))
import System.Info (os)
import System.IO (hFlush, stdout)

main :: IO ()
main = do
  ...

アルファベット順に綺麗に並びました。

vim-haskell-sort-importは、import文が改行で分かれていると各ブロックごとにソートするようになっています。 例えば、標準ライブラリーとローカルのライブラリーでごちゃまぜにして欲しくないということがあるでしょう。

import Data.Monoid
import System.IO (hFlush, stdout)
import Data.List (foldl')
import Control.Monad (forM_, when)

import XXX
import CCC
import DDD
import ZZZ
import AAA

main :: IO ()
main = do
  ...

これにHaskellSortImportを掛けると、次のようになります。

import Control.Monad (forM_, when)
import Data.List (foldl')
import Data.Monoid
import System.IO (hFlush, stdout)

import AAA
import CCC
import DDD
import XXX
import ZZZ

main :: IO ()
main = do
  ...

標準ライブラリーとごっちゃにならなくて安心です。

他にも、import文が複数行に跨る場合でもきちんと扱えます。

import System.IO ( hFlush,
                   stdout )
import Data.Monoid
import Data.List ( foldl',
                   mapAccumL,
                   mapAccumR )
import Control.Monad ( forM_,
                       unless,
                       when )

これに:HaskellSortImportすると、次のようになります。

import Control.Monad ( forM_,
                       unless,
                       when )
import Data.List ( foldl',
                   mapAccumL,
                   mapAccumR )
import Data.Monoid
import System.IO ( hFlush,
                   stdout )

コードは壊れていません。 安心です。

いちいちコマンドを叩くのは面倒なので、私はファイルを保存したら常にソートするように設定しています。 rtpの通っているどこかのftplugin/haskell.vimに、次のように書いておくと良いでしょう。

autocmd BufWritePre <buffer> HaskellSortImport

あるいは別の方法としては、vimrcに次のように書いておくとうまく動きます。

augroup vimrc-haskell-sort-import
  autocmd!
  autocmd BufWritePre *.hs HaskellSortImport
augroup END

ファイルを保存する度に容赦なくソートされます。 この設定をそのままにしつつ、一時的にソートせず保存したいときは、noa wすると良いと思います。

そんなわけで、Haskellのimport文をソートしてくれるプラグインを作りました。 VimHaskellを書く方にはとても便利なプラグインだと思いますので、ぜひお使い下さい。 github.com