Dhallとはなにか Dhallは今まで挙げたすべての特徴を備えた設定ファイル言語です。つまり、型と関数とインポートの機能があります。そして、無限ループは起こりません。チューリング完全ではないです。さらに、副作用は標準のDhallでは存在しません。 読みは「dɔl」、この発音記号の読み方がわからないですけど、こんな感じです。カタカナでは「ダール」もしくは「ドール」だと思います。このスライドでは「ダール」で通させていただきます。 まずDhallの開発体制なんですけど、言語仕様は特定の実装に依存せずに、独立して管理されています。dhall-langという、GitHubのコミュニティでしたっけ？ GitHubの組織の下にdhall-langというリポジトリがあって、そこで管理されています。その上で、主要ツールおよび言語仕様の参照実装はHaskellで書かれています。これはdhall-langの下にあるdhall-haskellというリポジトリで管理されています。 Dhall、型ってちょっとさっき言いましたが、どんな型を持っているかを見ていきたいと思います。 まず、プリミティブな型としては、Bool、Natural、Integer、Double、Textという、真偽値とか整数とか浮動小数点数とか、あと文字列とか、そういうのがあります。一般的な感じじゃないでしょうか。 ですけれども、おすすめの方法なのですが、数値の型はできるだけNaturalを使うのがおすすめです。Naturalは一番使える標準関数が多いですので、使い回しがよいです。 さて、次に複合型です。 ComplexTypeですね。型を引数にとる型みたいな感じです。それにはListとOptionalとRecordとUnionがあります。 Listはリストですね。Optionalは0〜1個の値をとるものです。最近の言語ではOptionとかMaybeとかって言われてるやつだと思います。Recordというのは、だいたいJSONのオブジェクトに相当するもので、key-valueのペアの集まりです。Unionというのもあって、これは「このうちのどれか1つの値が入る型」みたいなものになります。 Unionの値の例のところを見ていただけるとわかると思うんですけど、ちょっと書き方が特徴的ですね。それはDhallの公式の人もわかっているみたいで、記述をサポートするための標準関数があるので、まずはこの記法はあまり責めないでください。この値の例にあるUnionの意味としては、AというラベルとBというラベルがあるUnion型で、Aを選んだときの値」みたいな意味になります。 インポート機能があります。 インポートは、ローカルのパスとURLからのインポートができます。ローカルのパスは、「./」とやって、あとパスを書くと、それはローカルパスからのインポートとしてDhallは判定して、そこにあるDhallファイルを読みにいきます。また、URLからのインポートもできて、それはhttpまたはhttpsから始まるものであったら、そこはURLからのインポートだとDhallは判定してやっていきます。 インポートにあたってハッシュ値のチェックを設けることができて、それによって「インポートするファイルが変わっているか」「いないか」みたいなチェックをすることもできます。 また、Dhallファイルではない生のテキストのインポートもできます。長いライセンス文章とか、自然言語文章をDhallファイル中に置きたいとなったときは、その機能を使えばOKです。

Dhallの導入について きっと「なるほど、Dhallは便利かもしれない。いいものかもしれない」となっていただけたと思うんですけど、「自分の使っている言語にはバインディングはないだろう」、また「すでにYAMLやJSONで設定ファイルを書いてしまっているし……」みたいなことをみなさん思われるんじゃないでしょうか。 実際、いまのところ、最新仕様に準拠したバインディングはHaskellのものしかないです。ScalaとRustがちょっと古い仕様に準拠したバインディングがあるぐらいですね。ですが、本当に導入は難しいのか？ Dhallの導入は簡単です。なぜかというと、dhall-to-yamlというのとdhall-to-jsonというのが提供されています。これは何かというと、DhallファイルをYAMLやJSONに変換するコマンドラインツールです。 つまり、お使いのプログラム言語の中でDhallを読むことを考えなくてもOKです。バインディングがない言語でもDhallを使えます。既存のYAMLを読む処理があったら、もうそれだけでOKになります。 導入もMacとLinuxでは簡単で、MacだとHomebrewで入ります。Linuxだとcurlコマンドで入りますね。WindowsはHaskell環境を入れる必要があるんですけど、それでもコマンドを何個か打てば入りますって感じです。

KubernetesのYAMLを書いてみる 例として、KubernetesのYAMLを書いてみるということをやってみたいと思います。 最近いろいろ話題のOSSのKubernetesというのがありますけど、これはYAMLを大量に用いることで有名です。実際に「Wall of YAML」「YAMLの壁」って言われているらしいですね。 本当にさまざまなYAML管理ソリューションがもうすでに提案されて存在しています。ですけれども、今回はDhallのdhall-to-yamlを用いて、安全かつ便利にKubernetesのYAMLを生成してみることにトライしていきたいと思います。 実はすでに「dhall-kubernetes」というのがあって、本当にKubernetesでYAMLを作るんだったらそれを使うほうがいいんですけど、今回はあくまで例題としてDIYでいきます。 まず、目的とするYAMLはこれです。これはKubernetesの公式サイトに載っているYAMLの例です。まずkindというキーの下にServiceとかデプロイメントとかの値のどれかが入っていると。apiVersionがv1とかなんか入ると。metadataはオブジェクトになっていて、nameというキーを持つなどあると。こういうYAMLがあるというわけですね。これをservice.yamlとします。 まず、愚直にDhallを書いてみます。 まずは、今までDhallには型があるとかいろいろ言ってきましたけど、とくにあまり意識せずに書いてます。kindは”Service”という文字列だと。apiVersionは”v1”という文字列だと。ほとんどそのままYAMLとそんな変わらない感じで書けます。 dhall-to-yamlを使って変換した結果がこちらとなります。キーの順番とかは入れ替わってますけど、同じYAMLができました。 型などを意識しなくても、目的とするYAMLはできます。ユースケースによっては、これぐらいでもまあまあ便利です。 さらにDhallの能力を引き出そうとするならば、型やデフォルト値などを用意することができます。基本的にはアイデアとしては、Union型を用いて記述できる値を制限する。Record型を用いてデフォルト値を用意するということをやっていきます。 さて、これがKubernetesのYAMLの型を定義するファイルの一部です。まずKindというのは、ServiceもしくはPod、もしくはDeploymentのどれかであるというUnion。ApiVersionは、”v1”という値を持つのであろうUnion。あとは、MetadataとかSelectorとかいろいろあると。いろんなRecord型があるというふうに定義していきます。 これUnionで表現して、ApiVersionとかはUnionで表現しているんですけれども、これらは単にDhall上の値でしかないので、YAMLのStringに戻す処理が必要です。そこで、makeYamlという関数を作ります。それでさっきのapiVersionとかのUnionをStringに戻します。実装はちょっと長くなっちゃうので省略します。

定義した型を利用して書き直す さて、先ほどの愚直に書いたやつを、型を利用して書き直してみましょう。 まず、1行目で型を定義したファイルを読み込んで、「k」という名前にします。3行目で、先ほど作ったmakeYamlという関数を呼んでいます。makeYamlの引数が、4行目以降ですね。kindは、kindのうちServiceというやつ、apiVersionはapiVersionのうちv1というやつ、みたいな感じで指定していきます。 それで、先ほどと同じようにdhall-to-yamlにかけるんですけど、結果は同じで。ただし、より安全にKubernetesのYAMLが表現できました。 例えば、これをServiceという文字をタイポしているとか、TCPという文字をTPCにしてるとか、そういう間違いを犯す可能性が最初のバージョンではあったんですけど、ここではもうそういった間違いを犯す可能性はなくなりましたというわけですね。 デフォルト値。これでもそれなりにいいんですけど、Kubernetes YAMLのデフォルト値を作ってみましょう、ということをやります。 型定義は、先ほど同じやつをk_types.dhallというのをそのまま使って、3行目・4行目はdefaultServiceを定義しています。Serviceなので、kindはService固定で、apiVersionはv1固定だとなります。6行目でtcpというRecordを定義しています。これはProtocols.TCPを持つポートだというわけですね。 デフォルト値を用意しておくと、同じような値をたくさん作るときに間違いをしにくくなるので便利です。ただし今回は紙幅の都合上、1つだけの場合を例示します。 それをどう使うかというと、こんな感じです。 k.makeYamlというところはさっきと同じですけど、defaultServiceというのを次の2行目で使っていて、それに「∧」の演算子でRecordを合成します。defaultServiceの値をそのまま使って、metadataとかspecとか、defaultServiceに定義されていないものを入れていきますという感じです。 Recordを合成する演算子はいくつかあるんですけど、ここではこの「∧」を使っているという感じです。 これも先ほどと同じように、dhall-to-yamlにかけると、同じYAMLが出てきますというわけですね。 さて、早いですが、まとめですね。 Dhallは設定ファイルとして限界を突き詰めた言語です。インポートや型とか関数とかの機能を持っています。それでありながら、副作用とか無限ループなどの危険はありません。 YAMLはJSONに変換できますので、既存の資産に組み入れることも容易です。とくにMacとLinuxはバイナリ配布があって、より導入しやすいです。 さらに、お使いいただくならば、ユースケースに合わせたレベルで利用することができます。インポートとデフォルト値があればいいだけの人もいると思いますし、ばりばり厳密に型を定義して誤りがないようにしたいという人もいます。それぞれのユースケースに合わせたレベルで利用することができます。

Dhallのより高度な使いかた まとめは以上ですが、まだ時間がありそうなので、ちょっと補遺のスライドをやっていきます。 「より高度な使い方」。dhallコマンドラインツールとか紹介しなかった言語機能とかやっていきます。 まず、dhallコマンドラインツール。このスライドではもっぱらdhall-to-yamlとかdhall-to-jsonをそのまま使っていきました。これは導入しやすいというのが大きな理由ですが、そのままそれだけで使ってきました。 今のところHaskell環境が必要になるんですけれども、dhallコマンドというのもあって、それを導入するとより幸せになることができます。 これはごく一部のサブコマンドなんですけど、「dhall format」というサブコマンドは公式のフォーマッタで、最近の言語にありがちな公式フォーマッタですね。私はDhallを書くときは、Dhallファイルを保存するときにdhall formatを自動的に走るようにしています。 「dhall repl」というサブコマンドもあって、これはDhallのインタラクティブ環境ですね、「この値がどうなっているか」みたいなのを手元で簡単に確かめるときには、これを使うと便利です。

紹介しなかった言語機能 これはあまり詳しくないのでそんなにないんですが、紹介しなかった言語機能として、Dhallは「多相な関数」とか「型の型」みたいなのもサポートしています。 多相な関数というのは、要するに複数の型の値を取りうる関数みたいな感じですね。ただし、Dhallの特徴として、いわゆる高度な型推論みたいのはほとんどありません。「それでどうやって多相な関数をやるんだよ？」というと、関数に型を渡します。引数として関数に型を渡すという感じで、多相な関数を実現します。 あと、型の型（カインド）。これは最近追加されたんですけど、型の作る型みたいな単位も普通に扱えるようになりました。 HaskellでDhallを直接読み込む。このスライドではもっぱらdhall-to-yaml・dhall-to-jsonを使って、DhallをYAMLとかJSONに変換する方法を見てきました。しかし、Haskellにはバインディングがあるので、YAML等を介さずにDhallファイルをHaskellの型として読み込むこともできます。 これはDhall、YAMLやJSON以外の形式に変換したい場合とか、あるいはHaskellプログラムの設定ファイルとしてそのまま使うみたいな使い方もできるわけですね。 HaskellでDhallを拡張する。HaskellによってDhallのビルトイン関数を追加することができます。標準のDhallにはできないダーティなことがそれによってできるかもしれません。例えばIO副作用をしてしまうとか、そういうことができるかもしれません。 ただし、標準ツールチェインの恩恵を受けられなくなるので、そこは注意が必要です。例えば、dhall formatとかが正しくフォーマットしてくれるかもわからないし、dhall replが正しくそれを評価してくれるかもわからないみたいな状態になります。それに合わせて標準ツールチェインも改造すればいいっちゃいいんですけど、それはちょっと大変です。

事例紹介 時間が最後までいったので、事例紹介です。 これは言っていいと言われていますので言いますが、所属している会社でDhallを使ってみましたという紹介です。会社で混沌のJSONが跋扈してまして……。 （会場笑） 何を以って混沌とするかというと、1〜3文字ぐらいの略語のキーしか使われていないみたいなJSONって、「俺はいったい何書いてんだ？」みたいになるわけですけど。 さらに、ヒントのない列挙型の値みたいなのを使って1とか2とか3とか書くんだけど、「この1とか2とか3ってどういう意味だ？」みたいになると。あとさらに、これJSONですらないんですよね。独自マクロがあるんですよ。 （会場笑） JSONのフォーマットとしてinvalidになるというひどい独自マクロもあったというわけなんですけど、これをがんばってDhallで全部定義し直して、上記の問題を解決しました。invalid JSONを作る必要があるので、dhall-to-jsonをそのまま使えないんですよね。そこでHaskellで変換処理を書いたというエピソードがあります。 （会場笑） ちょっと時間余っちゃいましたけど、終わりですね。 司会者：ありがとうございました。 （会場拍手）

Dhallの関数をJSONに変換できるか？ 司会者：質問のある方はいらっしゃいませんか？ 質問者1：すいません。純粋に話に置いていかれたので教えてほしいんですけど、makeYamlって定義したじゃないですか。あれはdhall-to-jsonでJSONにもできるんですか？ そのへんがちょっとわからなかったんですけど。 syocy：ええっと……？ 質問者1：これDhallファイルじゃないですか。このファイルにdhall-to-jsonをかけると何が起こるんですか？ syocy：あっ、makeYaml自体をJSONにできるかってことですか？ それはサポート外ですね。 質問者1：なるほど。じゃあこれはもうdhall-to-yamlしかできないというDhallファイルですか？ syocy：そうですね。 質問者1：なるほど、ありがとうございます。 syocy：Dhallの関数をJSONとかに変換することはできなかったはずです。エラーになるはずです。 司会者：なので、ここではmakeYamlが返したDhallのRecordをJSONに変換するかたちになります。 syocy：そういうことですね。 司会者：はい。ほかに質問がありますか？ ありがとうございます。 質問者2：プログラミング言語としてDhallを見たときに、「ここがもう少しこうだったらいいのにな」と思っていることとかってあるでしょうか？ syocy：OptionalとListのリテラルが同じなんですよ。括弧で書いて、「その鍵括弧はListである」とか「この鍵括弧はOptionalである」みたいな型注釈を入れないといけなかったんですよね。それはかなり大変でした。 ですけれども、一番最近の修正でそこにだけ型推論が効くようになったんですよね。Optional、sumとかNumとか書くと、そこに型推論が効いて、「これはOptionalの値だ」みたいなことをやる。そういう修正が入ったので、今はあまり不満がないですね。 司会者：ほかにありますか？

YAMLファイルをDhallの形式に直す 質問者3：大量のYAMLファイルとかがいま実際あったりすると思うんですけど、それを逆にDhallにするときにはどうやってやるんですか？ 要は、dhall-to-yamlとかdhall-to-jsonはあるんですけど、逆はどうやってやるんでしょうか？ syocy：それは、YAMLファイルをDhallの形式に直すということですか？ 質問者3：そうですね。 syocy：もしくは、そのYAMLファイルから自動的にDhallの型みたいなのを……。 質問者3：そうですね。自動的にそういうかたちにしてくれるような、何か素敵なツールがあるとうれしいんですけど（笑）。 syocy：今のところ、そういうのはないですね。 質問者3：なるほど、そういう動きは、世の中的にもないんですか？ syocy：たぶんその動きはないと思います。今のところ、がんばって、ただJSONの型を人間が読み取って型をつけていくみたいな感じです。 質問者3：そうですよね。ありがとうございます。 司会者：きっと職人芸が求められる感じじゃないですか。ほかに質問のある方はいらっしゃいますか？ ありがとうございます。