DhallでFizzBuzzしよう

# 設定ファイルのアーミーナイフ的言語 Dhall
Dhallは便利な言語ですが、再帰を行う関数を許容しないため、フィルターなどを書くときに一癖あります。

書く時のコツをFizzBuzzを例に紹介したいと思います。


FizzBuzzは３の倍数の時`Fizz`、５の倍数の時`Buzz`両方に一致するとき（１５の倍数の時）`FizzBuzz`と表示する、言語の文法解説などで使われるプログラムです。


Dhallで面倒なのは倍数を確認する部分で、標準的な機能の`Prelude`には割り算や余りを求める関数がありません。そのため実現には工夫必要になります。


# データを考える
プログラムで表現する状態は、Fizz、Buzz、FizzBuzz、普通の数字の４つです。HaskellやPureScriptのデータ型は`Union`と呼ばれる方法で表現します。ちなみにV10から表現方法が変化しましたので、昔のものをコピペしても動かないことがあります。

``` Haskell
let FBdata : Type = <Fizz : Natural | Buzz : Natural | FizzBuzz : Natural | Normal : Natural>
```

鍵かっこで囲っている以外はHaskellライクなので、見慣れた感じです。

最終的に文字列になるので、文字列に変換する関数を定義します。

``` Haskell
let FBdata/show
      = λ(d : FBdata)
      → merge
          { Fizz = λ(t : Natural) → "Fizz"
          , Buzz = λ(t : Natural) → "Buzz"
          , FizzBuzz = λ(t : Natural) → "FizzBuzz"
          , Normal = λ(n : Natural) → Natural/show n
          }
          d
```

`merge`は、組込み関数として使用でき、パターンマッチを書きたいときに使用します。`case of`構文の代わりぐらいの認識ですが、もっと面白い使い方の記事とかがあれば私が喜びます。

# 倍数チェック

プログラムの重要な部分である倍数をチェックする部分です。戦略としてはチェックする数字がFizzBuzzの倍数になっているかを見るのではなく、３の倍数及び５の倍数のListを必要なだけ作成して、チェックする数字がListに含まれるかを確認します。

まず、等差数列を作成する関数を書きます。

``` Haskell
let Prelude = https://raw.githubusercontent.com/dhall-lang/dhall-lang/v10.0.0/Prelude/package.dhall

let numlist
      = λ(n : Natural)
      → λ(pitch : Natural)
      → Prelude.List.generate n Natural (λ(x : Natural) → x * pitch)
```

最新の`Prelude`はコンパイルが通らなかったので、V10のものを指定しています。

チェック関数は、１つの数字を渡してその数字が、Fizzリスト、Buzzリストに含まれるかを確認します。両方に該当する場合、FizzBuzzとして値を返します。

```Haskell
let check
      = λ(n : Natural)
      → let fizzy = Prelude.List.any Natural (Prelude.Natural.equal n) (numlist n 3)
        let buzzy = Prelude.List.any Natural (Prelude.Natural.equal n) (numlist n 5)
        let checked =
            if    fizzy && buzzy then FBdata.FizzBuzz n
            else  if fizzy && Prelude.Bool.not buzzy then FBdata.Fizz n
            else  if Prelude.Bool.not fizzy && buzzy then FBdata.Buzz n
            else  FBdata.Normal n
        in checked
```

確認のたびにｎまでのFizzリスト、Buzzリストを生成しているので、巨大な数を確認する場合は、ほかの方法をとったほうが良いと思います。

# 出力

今までの関数を繋げて完了です。必要なだけの長さのリストを渡して、各要素ごとにチェック関数を適用させ、FBdataのリストを作成します。`FBdata/show`を通して、文字列の読める形にします。

```Haskell
in  let fizzbuzz = Prelude.List.map Natural FBdata check (numlist 20 1)
    in  Prelude.List.map FBdata Text FBdata/show fizzbuzz
```

すべてまとめて以下のようになります。

```Haskell:fizzbuzz.dhall
let Prelude = https://raw.githubusercontent.com/dhall-lang/dhall-lang/v10.0.0/Prelude/package.dhall

let FBdata : Type = <Fizz : Natural | Buzz : Natural | FizzBuzz : Natural | Normal : Natural>

let FBdata/show
      = λ(d : FBdata)
      → merge
          { Fizz = λ(t : Natural) → "Fizz"
          , Buzz = λ(t : Natural) → "Buzz"
          , FizzBuzz = λ(t : Natural) → "FizzBuzz"
          , Normal = λ(n : Natural) → Natural/show n
          }
          d

let numlist
      = λ(n : Natural)
      → λ(pitch : Natural)
      → Prelude.List.generate n Natural (λ(x : Natural) → x * pitch)

let check
      = λ(n : Natural)
      → let fizzy = Prelude.List.any Natural (Prelude.Natural.equal n) (numlist n 3)
        let buzzy = Prelude.List.any Natural (Prelude.Natural.equal n) (numlist n 5)
        let checked =
            if    fizzy && buzzy then FBdata.FizzBuzz n
            else  if fizzy && Prelude.Bool.not buzzy then FBdata.Fizz n
            else  if Prelude.Bool.not fizzy && buzzy then FBdata.Buzz n
            else  FBdata.Normal n
        in checked

in  let fizzbuzz = Prelude.List.map Natural FBdata check (numlist 20 1)
    in  Prelude.List.map FBdata Text FBdata/show fizzbuzz
```

実行させれば、ちゃんと実行できていることがわかります。

```bash:bash
> cat fizzbuzz.dhall | dhall-to-json
["0","1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz","16","17","Fizz","19"]
```

Dhallの関数を使用することで、手で書くには面倒な記載を自動化することができます。逆に関数で記載が難しいような場合には、べた書きしてしまえば良いため、導入の心理的な障壁も小さいです。いろいろな出力先があるため、ちょっとでも、一部だけでも、一回だけでも使ってみてください。

Twitterをやってますので、もし宜しければフォローしてみてください。[@noolbar](https://twitter.com/noolbar)