2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DhallでFizzBuzzしよう

Posted at

設定ファイルのアーミーナイフ的言語 Dhall

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

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

FizzBuzzは3の倍数の時Fizz、5の倍数の時Buzz両方に一致するとき(15の倍数の時)FizzBuzzと表示する、言語の文法解説などで使われるプログラムです。

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

データを考える

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

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

鍵かっこで囲っている以外は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の倍数になっているかを見るのではなく、3の倍数及び5の倍数のListを必要なだけ作成して、チェックする数字がListに含まれるかを確認します。

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

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のものを指定しています。

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

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

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

出力

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

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

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

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
> 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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?