KotlinでFizzBuzz 単語

コトリンデフィズバズ

7.5千文字の記事
これはリビジョン 2654837 の記事です。
内容が古い・もしくは誤っている可能性があります。
最新版をみる

KotlinでFizzBuzzとは、Kotlinの記事内に書かれるべきサンプルコードであるが、あまりにも巨大なため分離することにした記事である。

概要

FizzBuzzをKotlinで書くにあたって、Kotlinらしさを出すならDSL(Domain Specific Language)ではないかと考えた。FizzBuzzを書く際に、if と for をDSLに実装できるのではないかと思っていたのを実践しつつ、言語の特長をコメントで紹介していたら、コードがあまりにも肥大化したので独立した記事にした。

無駄なことをしたと反省はしているが、以前から if と for はKotlinでは文法に規定しなくてもDSLの記法で書けるのではないかと疑問に思っていたので後悔はしていない。

コード

package flowcontroldsl
/*
* "for" and "if" features implementation without using them
* to show the flexibility of Kotlin DSL(Domain Specific Language).
* Good boys and girls should not try.
*/

/**
* エントリーポイントはトップレベル関数のmain().
* @param args Kotlin 1.3からは、この引数も省略可能になった
* @return [Unit]型(Javaのvoid相当)の戻り値型は省略可能
*/
fun main(args: Array<String>) {
fizzBuzz1()
fizzBuzz2(); fizzBuzz3() // ;は不要だが、使って1行に複数の文を書くことも可能
fizzBuzz4(31..40)
fizzBuzz5()
fizzBuzz6()
fizzBuzz7()
}


/**
* その気になればDSLを作ってifを自分で定義することも可能.
*
* 予約語も``を使えば識別子に使用可能。
* 本来はSystem.`out`などJavaで予約語が使われていた時用なので、
* 良い子はまねをしてはいけない。
* @param condition 条件
* @param trueOperation [condition]がtrueの時に実行されるラムダ式
* @param R if elseは式であり、値を返すが、その戻り値の型
* @return [condition]がfalseならnull。
* trueなら[trueOperation]の実行結果を[IfResult]でラップした値。
*/
fun <R> `if`(
condition: Boolean, // 型名は後置
trueOperation: () -> R // 関数型の型名は(引数型) -> 戻り値型で記述
): IfResult<R>? { // ?はnullableの証
var ifResult: IfResult<R>? = null // varを使えば再代入可能

// ifの定義なのであえてifを避けた。大抵の言語にある論理演算の短絡評価を使用。
condition && { -> // ラムダ式は{}内に記述。引数なしなら->は省略可能。
ifResult = IfResult(trueOperation.invoke())
true // ラムダ式は最後の行が値を返す
}() // 関数型インスタンス(引数)で関数呼び出し。.invoke()相当。

return ifResult // 関数定義内の戻り値はreturnで返す
}

/**
* [R]がnullableの場合を考えると、conditionがfalseの場合を伝えるのに
* ラッパークラスが必要.
*
* クラスはコンストラクタやプロパティ(Javaのフィールド)と同時に定義可能。
* getterやsetterは必要ない。
* @property result ラップする値
* @see [else]
*/
class IfResult<R>(val result: R)

/**
* クラス定義はデフォルトでpublic finalだが、
* 拡張関数により、クラス定義外でもインスタンスメソッドを追加定義できる.
* ここでそうする必要はないが、サンプルとして。
*
* 拡張関数内ではクラス定義内部と同じように[this]が使える。
* この[this]が指すインスタンスのことをレシーバーと呼ぶ。
* レシーバーと呼ばれる由来がはっきりと書かれたところがないのだが、
* フォーラムでの用語の使い方を見るに、
* どうやらインスタンスメソッドが定義されているとみなして、
* メソッドの呼び出し(call)を受ける(receive)インスタンスだから
* レシーバー(receiver)ということのようだ。
*
* また、今回は紹介できないがfinalなクラスでも継承するinterfaceがあれば
* 委譲(delegate)が使える。
* infixで中置演算子記法も使用可能。
* @receiver [if]の結果
* @param falseOperation thisがnullの時に呼ばれる処理
* @return if式の値
*/
infix fun <R> IfResult<R>?.`else`(falseOperation: () -> R): R {
// this?.resultはthisがnullの時resultは評価せずにnullを返す
// ?:はエルヴィス演算子と呼び、左項がnullでなければ左項の値、nullなら右項の値を返す
return this?.result ?: falseOperation()
}
/**
* else if 用 オーバーロード.
*
* 本物のif式との違いは[other]を生成するための条件式が必ず評価されてしまい、
* 短絡評価にならないこと。
* 通常、条件式に状態変更する内容が入ることはないが、
* 例外を投げるような式が入っていると問題になる可能性が残る。
* if ({条件のラムダ式}) {処理} とすれば解決できるが、見栄えの問題でやらない。
* @receiver この`else`の前までの[if]や`else` `if`の結果
* @param other この`else`の次の[if]の実行結果
* @return 次の[else]に渡す結果
*/
infix fun <R> IfResult<R>?.`else`(other: IfResult<R>?): IfResult<R>? {
return this ?: other
}


/**
* DSLの見栄えのために、クラス名の頭文字を大文字にするという原則を無視することは、
* Kotlin in Actionでも認められている。
* @see [For]
*/
typealias `for`<T> = For<T>

/**
* for文をDSLとして実装.
*
* continue, breakは例外処理で実現。ラベルのジャンプまで実装できた。
* @param T 繰り返し処理対象の型
*
* @constructor
* Kotlinのfor文は for (e in iterable) {} の書式だが、
* ここでは`for` (iterable) { e -> } という記法になる.
* @param iterable 繰り返し処理の対象の[Iterable]
* @param operation 繰り返し処理。operation内部では、
* [Throwable]をcatchするtry式の中で[continue][break]を呼んではならない。
* [Exception]をcatchするtry式内で呼ぶのは問題ない。
*/
class For<T>(iterable: Iterable<T>, operation: For<T>.(T) -> Unit) {
/*
* ラムダ式がコンストラクタの引数と解釈されるので、
* 関数型オブジェクトにしてinvokeメソッドで起動する方法は無理で、
* operationはコンストラクタ内で起動するしかない。
*/
init {
/**
* あえて末尾再帰最適化tailrecを用いてループ構成する.
*
* [Iterable.forEach]を使えば簡単だが、
* [Iterable.forEach]のソースにforが使われているので。
*/
tailrec fun recursive(iterator: Iterator<T>) {
try {
operation(iterator.next())
} catch (e: ContinueException) {
`if` (e.destination !== this) {
throw e
}
}

// 通常のifでないとtailrecが効かないので
// `if`には中断する処理を入れた。
`if` (!iterator.hasNext()) {
this.`break`() // `if`内でreturnできないのでやむなく
}
return recursive(iterator)
}

try {
recursive(iterable.iterator())
} catch (e: BreakException) {
`if` (e.destination != this) { // 本当の参照等価は=== 上記参照
throw e
}
}
}

/**
* 本来は[Exception]を継承するべきところだが、それだと
* try {} catch (e: Exception) {}のような文脈で[break][continue]
* throwする例外がcatchされて処理を抜けられない.
*
* try {} catch (e: Throwable) {} ではもちろんcatchされてしまうが、
* そもそも[Error]もcatchしてしまうようなコードは本来書くべきではないので、
* サポート対象外ということにする。
*/
private class BreakException(val destination: For<*>) : Throwable()

/**
* Kotlinでもinnerをつければインスタンスに内部クラスを定義することができるが、
* inner classで[Throwable]を継承するのはKotlin 1.3からエラーになる。
* 1.2でも思うように動作しなかった。
* インスタンスごとに例外を定義するのはNGで、staticな内部クラスに
* しなければならないようだ。
*/
private class ContinueException(val destination: For<*>) : Throwable()

/**
* for文のbreak相当.
* [Throwable]をcatchするtry式の中で呼んではならない。
* [Exception]をcatchするtry式内で呼ぶのは問題ない。
*
* @param destination ラベルの代わり
* @throws BreakException 戻り値型が[Nothing] = 必ず例外を投げる
*
* @see [continue]
*/
fun `break`(destination: For<*> = this): Nothing { //デフォルト引数が使える
throw BreakException(destination)
}

/**
* Equivalent to "continue" in "for" sentence.
* Do not call this method in try expression where [Throwable] is catched.
* You can call this method in try expression which catches [Exception].
*
* return@Forで代用できるが、[if]の中など代用できない場合もある。
* @param destination ラベルの代わり
* @throws ContinueException 投げた例外は、[destination][for]でcatchされる
*/
fun `continue`(destination: For<*> = this): Nothing
= throw ContinueException(destination)

}


/**
* Kotlinらしさを主張した模範的FizzBuzz.
*/
fun fizzBuzz1() {
for (i in 1..10) { // ..演算子で生成するRangeは「閉」区間
when { // when式で複数分岐のif else地獄にさよなら
i % 15 == 0 -> "FizzBuzz" // case文のbreak忘れにもさよなら
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "$i" // string templateでフォーマット文字列も簡単
}.let(::println) // スコープ関数で変数定義を省けたりする
}
}

/**
* [fizzBuzz1]よりもちょっとハードコーディングっぽいFizzBuzz.
*
* サンプルなので無駄なこともしている。
*/
fun fizzBuzz2() {
for (i in 11 until 21) { // 左閉半開区間は..でなくuntil
val any = when (i % 15) { // 変数宣言はイミュータブルなvalが基本
in setOf(3, 6, 9, 12) -> "Fizz" // ListもSetもイミュータブル
in mutableListOf(5, 10) -> "Buzz" // 変更可能なのはMutableList
0 -> "FizzBuzz" as Any // キャスト
else -> i // 文字列にしないとwhen式はAny型を返していることになる。
}
print("""
$any

""".trimIndent()) // raw stringもstring templateと併用可能。
}
}

/**
* whenでなくif elseを使ったFizzBuzz.
*
* [println]の引数に式を書く方法ではif式の結果を2回使用する時には
* 変数宣言を省略できないが、
* [fizzBuzz1]でしたようなスコープ関数[let]を用いる方法なら可能である。
* ただし、[let]の呼び出しのほうがelse ifの結合より先なので、
* if式全体を括弧に入れる必要がある。
*/
fun fizzBuzz3() {
for (i in 21..30) { // Javaのfor-each文相当。Kotlinにfor(;;)はない。
println(if (i % 15 == 0) {
"FizzBuzz"
} else if (i % 3 == 0) {
"Fizz"
} else if (i % 5 == 0) {
"Buzz"
} else {
"${i}" // string templateは本来は{}で囲む
})
}
}

/**
* ワンライナー的FizzBuzz.
*
* [Sequence]にすると遅延評価になり[iterable]が巨大でもmap()が終わるまで待たずに済む。
* ラムダ式は変数名をitにすると変数名定義を省略可能。
* [run]の引数になるラムダ式は拡張関数で
* レシーバー(=[run]を呼び出したオブジェクト: String)はthisで表される。
*/
fun fizzBuzz4(iterable: Iterable<Int>) = iterable.asSequence().map {
"${if (it % 3 == 0) "Fizz" else ""}${if (it % 5 == 0) "Buzz" else ""}"
.run { if (isNotEmpty()) this else "$it" }
}.forEach(::println)

/**
* 関数の最後の引数がラムダ式の場合は括弧の外にラムダ式を書けるという仕様により、
* if式やfor文を自分で定義したDSL(Domain Specific Language)の記述が可能.
*/
fun fizzBuzz5() {
`for`(41..50) { i ->
// fizzBuzz2()のように型推論で変数宣言の型は省略可能
val string: String = `if` (i % 15 == 0) {
"FizzBuzz"
} `else` `if` (i % 3 == 0) {
"Fizz"
} `else` `if` (i % 5 == 0) {
"Buzz"
} `else` {
"$i"
}
println(string)
}
}

/**
* FizzBuzzから目的がずれてきているが、
* [for][For.continue][For.break]のデモンストレーション.
*/
fun fizzBuzz6() {
`for`(41..70) { i ->
// Exceptionインスタンスをcatchするtry式内でも動作することの実証
try {
`if` (i <= 50) { // 残念ながら中括弧は省略できない
`continue`()
} `else` `if` (60 < i) {
`break`()
}
} catch (e: Exception) {
println(e.message)
}

println(toFizzBuzz(i))
}
}

/**
* DSLで作った[for]でもlabelが使用できることのデモンストレーション.
*/
fun fizzBuzz7() {
`for`(6..9)label@ { i ->
`for`(i * 10 + 1 .. (i + 1) * 10) { j ->
println(toFizzBuzz(j))

`if` (j >= 70) {
//`continue`(this@label)
`break`(this@label)
}
}
}
}

/**
* 数値をFizzBuzzに変換.
*
* [fizzBuzz2]以上にハードコーディングっぽい。
* @param i Kotlinは表向きはプリミティブ型がないのでintでなくInt型
*/
fun toFizzBuzz(i: Int): String {
// Mapの生成は中置演算toを用いて下記のように書ける
val fizzBuzzMap = mapOf(
0 to "FizzBuzz", // ここの型はPair<Int, String>
3 to "Fizz",
6 to "Fizz",
9 to "Fizz",
12 to "Fizz",
5 to "Buzz",
10 to "Buzz"
)
// MapやListのget()は[]でできる
return fizzBuzzMap[i % 15] ?: i.toString()
}

実行結果

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz

関連項目

  • Kotlin
  • FizzBuzz
  • 無駄遣い
  • 無駄に洗練された無駄の無い無駄な動き
  • ついカッとなってやった
  • 哀れな努力

おすすめトレンド

ニコニ広告で宣伝された記事

記事と一緒に動画もおすすめ!
もっと見る

急上昇ワード改

最終更新:2025/12/20(土) 17:00

ほめられた記事

最終更新:2025/12/20(土) 17:00

ウォッチリストに追加しました!

すでにウォッチリストに
入っています。

OK

追加に失敗しました。

OK

追加にはログインが必要です。

           

ほめた!

すでにほめています。

すでにほめています。

ほめるを取消しました。

OK

ほめるに失敗しました。

OK

ほめるの取消しに失敗しました。

OK

ほめるにはログインが必要です。

タグ編集にはログインが必要です。

タグ編集には利用規約の同意が必要です。

TOP