C#でコードを書いていると必ず式と文を使用します。
参考:C# の式と文の一覧
今回は文を書かずに式のみを使用してコードが書けるかどうかを検証します。
(ネタ記事です)
レギュレーション
- 文を使わない
- メソッドは式形式のMain関数のみとする
二番目のレギュレーションについて補足です。
namespace App
{
class Program
{
public static int Main() => /* ここ */;
}
}
上記のコードのここ
と書かれた部分にコードを記述します。
実装する課題
シンプルにFizzBuzz問題を実装します。
1-100までのFizzBuzzを標準出力に出力します。
解答
LINQを使用する場合
LINQを使用すれば比較的簡単に実装できます。
以下のコードで1から100までのfizzbuzz問題の解答文字列を得ることができます。
Enumerable
.Range(1, 101)
.Select(i =>
i % 3 == 0 && i % 5 == 0 ? "fizzbuzz" :
i % 3 == 0 ? "fizz" :
i % 5 == 0 ? "buzz" :
i.ToString()
)
問題になるのは標準出力に出力する方法です。
式全体で何等かの値を返さないといけない関係上ToListしてForEachという手法は使用できません。
また、Console.WriteLineの戻り値がvoidなためそのままでは使用できません。
// Console.WriteLineがvoidなのでこういうことができない
.Select(s => Console.WriteLine(s))
.Sum();
戻り値がvoidのメソッドを任意の型の戻り値に変換するために以下の手法を使用します。
// DynamicInvokeを使用するとnullを戻り値として取得することができる
T Transform<T>(Action a) => a.DynamicInvoke() == null ? default(T) : default;
// 戻り値voidがintに変換可能!!
Transform<int>(() => Console.WriteLine());
組み合わせると解答は以下の通りです。
static int Main() =>
Enumerable
.Range(1, 100)
.Select(i =>
i % 3 == 0 && i % 5 == 0 ? "fizzbuzz" :
i % 3 == 0 ? "fizz" :
i % 5 == 0 ? "buzz" :
i.ToString()
)
.Select(s => new Action(() => Console.WriteLine(s)).DynamicInvoke() == null ? 0 : 0)
.Sum();
別解
Console.Out.WriteLineAsync
を使用することでも解決できます。
static int Main() =>
Enumerable
.Range(1, 100)
.Select(i =>
i % 3 == 0 && i % 5 == 0 ? "fizzbuzz" :
i % 3 == 0 ? "fizz" :
i % 5 == 0 ? "buzz" :
i.ToString()
)
.Select(s => Console.Out.WriteLineAsync(s).ContinueWith(t => 0).Result)
.Sum();
LINQを使用しない場合
C#でコードを書くときにLINQを使用できないようなこともあるかもしれません。
今回はLINQを使用しない場合についても検証します。
LINQを使用しない場合はisを使用したパターンマッチングが使用できます。
これはC# 7から使用できるようになったもので、式中で変数が宣言できる神機能です。
class Hoge {}
var o = /**/
if (o is Hoge h)
{
// oがHogeの場合はhに代入されている
}
参考パターン マッチング - C# によるプログラミング入門
このパターンマッチは式が書ける任意の場所に置くことができます。
つまり以下のようなことが可能です。
// 式中でi,jを宣言して足し算する
// 1 + 2 = 3;
Console.WriteLine(1 is var i ? 2 is var j ? i + j : default : default);
// 再帰関数も作れる
// null is var は常にtrueになるので変数宣言に使用できる
var res = (Func<int, int>)null is var fib ?
(fib = n => n < 2 ? n : fib(n - 2) + fib(n - 1)) is var _ ?
fib(10)
: default
: default;
// fib(10) = 55;
Console.WriteLine(res);
再帰関数がかけるのならばfizzbuzzも解けるはずです。
解答は以下の通りです。
static int Main() =>
new Func<int, string>(i =>
i % 3 == 0 && i % 5 == 0 ? "fizzbuzz" :
i % 3 == 0 ? "fizz" :
i % 5 == 0 ? "buzz" :
i.ToString()
) is var f ?
(Func<int, int, object>)null is var rec ?
(rec = (n, stop) => n < stop ?
new Action(() => Console.WriteLine(f(n))).DynamicInvoke() is var _ ?
rec(n + 1, stop)
: default
: default) is var _ ?
rec(1, 101) == null ? 0 : 0
: default
: default
: default;
最後に
ほとんど役に立たないと思います。
isのvarパターンの使用方法について考えているとこのように使用しろと言われている気がしました
役に立たないC#関連ページ
おまけ
LINQもパターンマッチも使用しない場合
static int Main() =>
(new Func<
Func<
Func<
Func<dynamic, dynamic>,
Func<dynamic, dynamic>
>,
Func<dynamic, dynamic>
>,
dynamic
>(Z =>
Z(f => t =>
t.Item1 == t.Item2 ? default :
t.Item1 % 3 == 0 && t.Item1 % 5 == 0 ?
new Action(() => Console.WriteLine("fizzbuzz")).DynamicInvoke() ?? f((t.Item1 + 1, t.Item2)) :
t.Item1 % 3 == 0 ?
new Action(() => Console.WriteLine("fizz")).DynamicInvoke() ?? f((t.Item1 + 1, t.Item2)) :
t.Item1 % 5 == 0 ?
new Action(() => Console.WriteLine("buzz")).DynamicInvoke() ?? f((t.Item1 + 1, t.Item2)) :
new Action(() => Console.WriteLine(t.Item1)).DynamicInvoke() ?? f((t.Item1 + 1, t.Item2))
)((1, 101))
))(
f => (new Func<dynamic, Func<dynamic, dynamic>>(x => f(y => x(x)(y))))
(new Func<dynamic, Func<dynamic, dynamic>>(x => f(y => x(x)(y))))
) != null ? 0 : 0;