2020年末からEmacsで始めるStandard ML開発

本稿は[ML Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ml)の7日目の記事です。

# 背景

2020年も終わろうとしていますね。
[【令和元年の注目エディタは！？】決定版！2019年の人気エディタランキング](https://job-draft.jp/articles/419)なんかにあるように、近年のEmacsのシェアは5%未満となっています。
そんなさなか、なぜかEmacsでStandard ML（以下、SML）を書かされる人がいるかもしれません。[^1]

Emacsは歴史ある古いエディタですが、パッケージで機能を拡張できます。
しかし、Emacsの公式レポジトリ[ELPA](https://elpa.gnu.org/)やメジャーなレポジトリ[MELPA](https://melpa.org/)を見てみると、SML向けのパッケージはsml-modeくらいしか見当たりません。

そこでSMLもEmacsも初心者の人のために、手軽でSMLが書きやすいEmacs環境を作りましたので紹介します。
とりあえず起動してみると一通りの機能が動くようになっています。
Emacsを使いこなせず「SMLって糞だな！」とならないよう、この記事が助力になれば幸いです。

[^1]: 授業とか

# 環境の概要

SML開発に使えるEmacsのパッケージをMELPA・GitHub・Gistから入れてあります。
普通のIDEには当たり前にある入力補完、オンザフライの静的解析、REPLとの連携などが使えます。

## SMLのBasisであるStringモジュールの入力補完の例

見切れてますが、型もポップアップで表示されます。

![company-mlton2-mini.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42721/bca1b59c-1f94-3ac8-bec7-18e7562bf764.png)

## SMLの型エラーをオンザフライに表示する例

静的解析が強いSMLはオンザフライチェックと相性が良いです。

![flycheck-mlton2-mini.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42721/6a1ade37-9222-2f93-c13d-f1f3d721df8b.png) 

## 選択したコードを`Ctrl+C Ctrl+R`で、Emacs上のREPLで実行する様子

コードを書きながらREPLで実行を試す、というのはよくある開発方法ですよね。

![sml-mode2-mini.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42721/ca3643c2-8b14-873d-429b-8efcdeb0219c.png)

# 必要なもの

- Emacs (>= 24.1)
- MLton (>= v20130715) または SML# (>= v3.4.0)
    - なくてもオンザフライの静的解析以外は動きます

# 使い方

1. [レポジトリにあるinit.el](https://github.com/yonta/sml-emacs/blob/main/.emacs.d/init.el)を、ローカルの`~/.emacs.d/init.el`にコピーする
    - 例えばコマンドなら、
      `wget https://raw.githubusercontent.com/yonta/sml-emacs/main/.emacs.d/init.el --output-document=~/.emacs.d/init.el`
2. Emacsを起動する
3. 初回のみ、Emacsが自動でレポジトリから必要ファイルをダウンロードしてセットアップする
4. `.sml`のファイルを開くと有効になる

## 静的解析にSML#コンパイラを優先して使いたい場合

MLtonコンパイラとSML#コンパイラが手元の環境に両方ある場合、オンザフライの静的解析を行うためにMLtonが優先されます。
SML#コンパイラの静的解析を優先して使いたい場合は、`init.el`ファイル内の`leaf sml-mode`以下にあるコメントを外してください。

```diff
@@ -67,7 +67,7 @@
 ;; sml-mode
 ;; https://www.smlnj.org/doc/Emacs/sml-mode.html
 (leaf sml-mode :ensure t
-  ;; :hook (sml-mode-hook . (lambda () (setq-local flycheck-checker 'smlsharp)))
+  :hook (sml-mode-hook . (lambda () (setq-local flycheck-checker 'smlsharp)))
   )

 ;; company-mlton
```

# 環境に入っているパッケージの詳細

## leaf.el

環境設定のインストールや設定を行える。
モダンな書き方でEmacsを設定できる。
またel-get拡張により、EmacsのパッケージをGitHubやGistから取得できるのも便利。

## sml-mode

言わずとしれたEmacsでSMLを書くためのメジャーモード。
コードの色付けやインデントや自動挿入（例えば`|`を入力したときなど）を提供する。
SMLのREPLとの連携もある。
例えば、コードを選択して`C-c C-r`を押すと、Emacs内でコードを実行してくれる。
他にも`M-x sml-`でTABを押すと多数の機能がある。

## company

入力補完をしてくれる。
今回は表示をモダンにするために追加のパッケージを入れている。
SML用のパッケージであるcompany-mltonが世の中にあったため採用した。
MLtonコンパイラが吐き出した型情報ファイルを使っているため、MLton自体がなくても動く。
ただし、入力補完候補にある`MLton.XXX`はMLtonでしか使えないので注意が必要。

## flycheck

オンザフライでコードチェックしてくれる。
静的解析が強いSMLとは特に相性が良い。
書いたコードをREPLでuseしたりコピペせずともチェックができとても便利。
静的解析にコンパイラを使うため、MLtonかSML# が必要。
最近のSML/NJでもできそうなので、気が向いたら作るかも。[^2]

[^2]: 以前のSML/NJは、最初のエラー1つのみしかあげないという仕様だった気がする

## smartparen

括弧を自動で挿入してくれる。
SMLではシングルクォートとバッククオートの補完をせず、コメント`(* *)`の補完をするようカスタマイズしてある。

## dumb-jump

コードの定義ジャンプを提供する。
多数の言語に対応しており、デフォルトでSMLにも対応している。
コード上で`M-.`で定義元へジャンプ、さらに`M-,`で元の箇所に戻ってこれる。

# 今回やらなかった・やれなかったこと

- SMLに限らない一般的なIDE機能の追加はやらなかった
    - 変数ハイライトや検索やアウトラインやリファクタなど
    - もちろん、Eamcsのパッケージには多数存在するので、お好みでカスタマイズできる
- 存在しない高度なIDE機能はやれなかった
    - カーソル合わせたら変数の型でてほしい
    - SMLのコードが間違ってたら、オートフィクスしてほしい
    - SMLのモジュール構造を認識してリファクタリングしてほしい
    - SMLコード向けの統一されたコーディング規約やLinterがほしい
- VS Codeでのセットアップ方法
    - VS CodeはWSLをサポートしているので、WindowsのVS Codeを使ってWSL内でSMLを書く、という選択肢がある
    - VS Code拡張のマーケットプレイスに[Standard ML](https://github.com/freebroccolo/vscode-sml)があるが、レポジトリが消えてしまった。
    - [GitHubで検索](https://github.com/search?q=standard+ml+vs+code)すると2つくらい動きそうなのがある

# まとめ

- SMLを書きやすいEmacs環境を紹介した
    - さらにカスタマイズすればいくらでも使いやすくできる
- SMLの開発環境はまだまだ弱い
    - エディタ機能もツールも少ない
    - 初心者に勧めづらい