• 作成:
  • 更新:

AtCoderをD言語で解きたくなったので環境を整えました, いや整えられてないです

AtCoderをD言語で解きたくなった

D言語がC++に対して勝る点

  • GCがついているのでメモリ管理が面倒ではない
  • rangeに対応しているのでsortなどを行うためだけにbegin, endを使う必要がない
  • rangeに加えてUFCSがあるのでデータと連ねて処理する時に直感的に記述できる
  • 標準で配列が動的構築に対応しているのでvectorのような長いコードを書く必要がない
  • 標準の文法に連想配列がありインターフェイスがわかりやすい
  • C++だとintの最大値がstd::numeric_limits<int>::max()で取得するところD言語だとint.maxで極めてわかりやすい
  • 標準ライブラリが充実している

D言語がC++に対して劣る点

  • GCがある分その分遅い
  • 私がC++の経験の方が多いのでD言語の標準ライブラリをあまり把握していない
  • 日本語資料があまりないので標準ライブラリの解説は英語を読む必要がある
  • AtCoderにあまり重要視されていない(C++はboostまでついててずるい)

D言語がHaskellに対して勝る点

  • 破壊的操作を容易に行えるためメモ化とかを頻繁に行う競技プログラミングでは面倒が少ない
  • 短い時間で高いパフォーマンスを達成するには破壊的更新が手っ取り早い
  • 一級市民のデータ構造がリストじゃなくて配列なので速いことが多い

D言語がOCamlに対して勝る点

  • OCamlは文法が好きじゃない
  • beginがある. 個人の好みではある
  • OCamlには型クラスもテンプレートも無いので関数呼び出しが面倒
  • Array.lengthとか
  • Printf.printfを一々書いていくのは面倒
  • 破壊的更新が容易と言ってもやっぱり関数型言語において2級市民感があり面倒さはある

D言語がRustに対して勝る点

  • 競技プログラミングのような書き捨てのコードでライフタイムを気にしたくない
  • GCがあるから標準ライブラリのAPIがシンプルに収まっている
  • グローバル変数書き換えがunsafeになるの実プロダクトでは有用ですが競技プログラミングだと微妙

D言語を使っていた時期

私は高校3年(2013年)の時期にncaq/dogd: FPS(開発中止) というものを作っていました.

この時期はD言語をバリバリ使っていましたが, それっきりD言語はほとんど使っていません.

さて最新の言語仕様に対応出来るでしょうか. 互換性はD2の間保たれてるから別に大丈夫そうですね.

Emacs向けの環境構築を行う

私がEmacsに引きこもるのは当然です.

d-mode

昔からEmacsに入れてました. 普通に使えました.

dfmt

最近の言語ではコマンドラインのコードフォーマットが用意されているのが普通になってきました.

  • C++: clang-format
  • JavaScript: prettier
  • Go: gofmt
  • Rust: rustfmt
  • Haskell: stylish-haskell, hindent(使ってない)

コードフォーマッタがIDEにべったり密結合せずに, どのテキストエディタやIDEでも手軽に使えるようになったのはたいへん良いことですね. やっと世界がEmacsの思想(Unixの思想か?)を理解し始めたかと思うと嬉しいですね.

競技プログラミングのような書き捨てのコードでも, いや競技プログラミングだからこそフォーマットは自動で行って欲しいです. 自分で気にしている暇はありませんが, 自分のコードを見返すこと自体はそこそこあります. スタイルが整っていると, バグも発見しやすくなります.

当然D言語でもdfmt というパッケージを使ってフォーマットが出来ます.

また, Emacsにもmelpaにdfmtパッケージがあります. これを使って以下のように設定することでセーブ時に自動でフォーマットしてもらうことが可能です.

;; -*- lexical-binding: t -*-

(with-eval-after-load 'd-mode
  (custom-set-variables
   '(c-default-style (cons '(d-mode . "java") c-default-style))
   '(dfmt-flags '("--max_line_length=80"))
   )
  (defun save-buffer-and-dfmt ()
    "セーブした後dfmt-bufferする.
dfmt-bufferを先にしたりbefore-save-hookを使ったりすると
保存がキャンセルされてflycheckの恩恵を受けられない
"
    (interactive)
    (when (buffer-modified-p)
      (save-buffer)
      (when (and (dfmt-buffer) (buffer-modified-p)) (save-buffer))))
  (define-key d-mode-map [remap indent-whole-buffer] 'dfmt-region-or-buffer)
  (define-key d-mode-map [remap save-buffer] 'save-buffer-and-dfmt)
  )

全体のインデントは保存時に行いますが, 編集中もなるべくdfmtのインデントレベルに合わせたいのでstyleをjavaに設定してます.

当初はindent_sizeは2にして, indent_styleはotbsにしようと思っていました. しかし, The D Style - D Programming Language を見ると, オールマンスタイルとインデントサイズ4を推奨していたので, 郷に従うことにしました.

絶対2スペースの方が良いと思うんですけどね.

DCD

DCDというD言語向けの自動補完プログラムがあって, Emacsのcompany-mode向けフロントエンドが存在します.

しかし, 本当にD言語で競技プログラミングを本格的に行うかわからないので, 今回は導入を見送ることにしました.

本格的にD言語でやるなら導入します.

LDCがビルドエラー

LDCって昔はLDC2と言われてて, 古いバージョンのLDCが存在していて, 実際ファイルもLDC2のものが多く存在するんですが, そのへんどういう経緯を持ってLDCという名前に統一されたんですかね? 調べても出てこない. 元のLDCは何処に行ってしまったんでしょう.

AtCoderはD言語にDMD64 v2.070.1とLDC 0.17.0とGDC 4.9.4で対応しています.

速度が重視されるものですので, 当然ながらLDCを使いたい所ですが, 残念ながらLDCは私のLLVMのコンパイル環境に対応していないのかビルドできないですし, そもそも0.17.0という古いバージョンは存在しません.

と思ったらdlang overlayに存在しないだけで ldc-developers/gentoo-overlay: The official Gentoo Portage overlay for LDC packages. には0.17.0存在してますね. これでインストールスクリプトでローカルに入れたりせずにパッケージマネージャでインストールできます.

sudo layman -o https://raw.githubusercontent.com/ldc-developers/gentoo-overlay/master/overlays.xml -f -a ldc

とすることでoverlayがインストールできました.

さて後はLDC2をインストールするだけ…

と思ったら以下のエラー.

% m '=dev-lang/ldc2-0.17.0'

These are the packages that would be merged, in reverse order:

Calculating dependencies / * ERROR: dev-lang/ldc2-0.17.0::ldc failed (depend phase):
 *   EAPI=4 is not supported
 *
 * Call stack:
 *            ebuild.sh, line 635:  Called source '/var/lib/layman/ldc/dev-lang/ldc2/ldc2-0.17.0.ebuild'
 *   ldc2-0.17.0.ebuild, line   6:  Called inherit 'cmake-utils' 'bash-completion-r1' 'versionator'
 *            ebuild.sh, line 326:  Called __qa_source '/usr/portage/eclass/cmake-utils.eclass'
 *            ebuild.sh, line 111:  Called source '/usr/portage/eclass/cmake-utils.eclass'
 *   cmake-utils.eclass, line 109:  Called die
 * The specific snippet of code:
 | *   	*) die "EAPI=${EAPI:-0} is not supported" ;;
 *
 * If you need support, post the output of `emerge --info '=dev-lang/ldc2-0.17.0::ldc'`,
 * the complete build log and the output of `emerge -pqv '=dev-lang/ldc2-0.17.0::ldc'`.
 * Working directory: '/usr/lib64/python3.5/site-packages'
 * S: '/var/tmp/portage/dev-lang/ldc2-0.17.0/work/ldc2-0.17.0'
... done!

!!! All ebuilds that could satisfy "=dev-lang/ldc2-0.17.0" have been masked.
!!! One of the following masked packages is required to complete your request:
- dev-lang/ldc2-0.17.0::ldc (masked by: corruption)

For more information, see the MASKED PACKAGES section in the emerge
man page or refer to the Gentoo Handbook.

なるほどEAPI 4なんて古いものにはもう対応していないと.

んーこの修正 pull requestなんて投げてももはやこんな古いパッケージ対応してくれるとは思えないですね. GPL v2なので ncaq/ncaq-overlay: ncaq personal gentoo overlay にコピーしてしまいましょう.

コピーして依存関係を修正しましたが, 当然ですがビルドエラーが起きますね. どうやらllvm 3を要求していて, llvm 6では動かないようです.

諦めることにします. まだD言語を本格的に使うか決めてないですし.

DMDもビルドエラー

DMDなら2.070という同じものが使えるので, ひとまずDMDで戦うことにしましょう. 私の程度の初心者向け領域なら定数倍最適化は意味のある数値ではありません.

と思ったらDMDも

final link failed: Nonrepresentable section on output

でビルドエラーです. GCCのリンカのバージョンアップについていけてないっぽい?

もう時間もないし仕方がないのでDMD 2.079.1を使うことにします. そんなに互換性の違いとかないでしょ. あったとしてもコンパイルエラーになってくれればペナルティは付かないので, ランタイムエラーにならないことを祈ることにします.

docker使うべきか

docker使えばこういう古い環境も簡単に再現できるので, ネイティブ派だったんですがちょっと使いたくなってきました.