20200807のC#に関する記事は4件です。

不動点コンビネータを用いた無名再帰関数の実行(C#, F#)

cf. 不動点コンビネータを用いた無名再帰関数の実行まとめ

不動点コンビネータとは

すごく簡単に言うと、「再帰じゃない関数を引数にとって、再帰関数と同じ動きをする関数に作り替えてくれる高階関数」です。

再帰関数の例として、例えばフィボナッチ数を返す関数をJavaScriptで書いてみると

function fibonacci(n) {
  if (n == 0) return 0;
  else if (n == 1) return 1;
  else return fibonacci(n-1) + fibonacci(n-2);
}

と書いてもいいわけですが、記事の都合で末尾再帰の形にして

function fibonacci(f1, f2, counter) {
  if (counter == 0) return f1;
  else return fibonacci(f2, f1+f2, counter-1);
}
function fibonacci_(n) {
  return fibonacci(0, 1, n);
}

とまあこんな感じの末尾再帰にしたfibonacciを、さらにカリー化して例にします。

上のfibonacciはfunctionで定義されていますが、これをカリー化したアロー関数(匿名関数)で書きたい場合は

const fibonacci = f1 => f2 => n => (n == 0) ? f1 : fibonacci(f2)(f1+f2)(n-1);

こんな感じで、再帰するには一旦fibonacciという名前の変数を作っておいてから書き換えてやらないといけないと思ったがそんなことはなかったぜ。

ここに不動点コンビネータであるfix関数があれば

const fix = /* ... */
const f = fib => f1 => f2 => n => (n == 0) ? f1 : fib(f2)(f1+f2)(n-1); // fは再帰関数ではない
const fibonacci = fix f

こんな感じで、再帰してないアロー関数(匿名関数)fを使って再帰関数fibonacciができあがります。

不動点コンビネータがあると嬉しい、というようなものではないのですが、まあ計算理論として興味深い、というぐらい。

不動点コンビネータ - Wikipediaも読んでおくといいでしょう。

不動点コンビネータその1 再帰関数で実装したもの

fixの方を再帰定義にしてよければ、割と簡単です。

ただし、引数を名前呼び(遅延評価)する言語と値呼び(正格評価)する言語でちょっと書き方が変わります。

C# と F# はどちらも後者になります。

fixの型を疑似的に表すと ((T1 => T2) => (T1 => T2)) => (T1 => T2) です。

C#の例です。Fixをスタティックメソッドにしておきました。
Fixを呼び出しているところの型引数Fix<int, Func<int, Func<int, int>>>がうっとうしいですが、C#のラムダ式には型がなく、外から与えてやる必要があるので、仕方ありません。型引数を省略しようとすると、今度はラムダ式をキャストして、型を明示しないといけません。

Fibonacci.cs
using System;

class Program
{
    static void Main(string[] args)
    {
        int fib40 = Fix<int, Func<int, Func<int, int>>>(fib => f1 => f2 => n => n == 0 ? f1 : fib(f2)(f1 + f2)(n - 1))(0)(1)(40);
        Console.WriteLine(fib40);
    }

    // name "Fix" is recursive
    static Func<T1, T2> Fix<T1, T2>(Func<Func<T1, T2>, Func<T1, T2>> f) =>
        f(x => (Fix(f))(x));
}

F# はOCamlベースで、カリー化されていて、型推論も強力なのですっきりしています。

Fibonacci.fs
let rec fix f =
    f (fun x -> fix f x)

let fib40 =
    fix (fun fib f1 f2 n -> if n = 0 then f1 else fib f2 (f1 + f2) (n - 1)) 0 1 40

不動点コンビネータその2 Zコンビネータ―で実装したもの

不動点コンビネータ―fixの方も再帰なしで定義する例として、有名なのはYコンビネーターですが、静的型付け言語だとYコンビネーターにはうまく型が付けられないので、代わりにZコンビネータ―を定義します。

Zコンビネータ―は、関数は再帰しませんが、型の定義が再帰します。

C# や F# では、独自のデリゲート型を定義することで、型の定義が再帰するような関数やメソッドの型を用意できます。ただ、デリゲート型をF#で扱うのはちょっとだけぎこちなくなります。

C# はこちら。Zの定義の中でラムダ式をRecursive<T1, T2>デリゲート型にキャストする形で型を明示しています。

FibonacciZ.cs
using System;

// type "Recursive<T1, T2>" is recursive
delegate Func<T1, T2> Recursive<T1, T2>(Recursive<T1, T2> f);

class Program
{
    static void Main(string[] args)
    {
        int fib40 = Z<int, Func<int, Func<int, int>>>(fib => f1 => f2 => n => n == 0 ? f1 : fib(f2)(f1 + f2)(n - 1))(0)(1)(40);
        Console.WriteLine(fib40);
    }

    static Func<T1, T2> Z<T1, T2>(Func<Func<T1, T2>, Func<T1, T2>> f) =>
        ((Recursive<T1, T2>)(g => f(x => g(g)(x))))
        ((Recursive<T1, T2>)(g => f(x => g(g)(x))));
}

F# はこちら。デリゲート型をインスタンス化し、呼び出しにInvokeメソッドを使うなど、オブジェクト指向的なデリゲート型の扱いがC#よりもあからさまになっています(C# もバージョン1の頃はnewでデリゲート型をインスタンス化していました)。

FibonacciZ.fs
// type "Recursive<'a, 'b>" is recursive
type Recursive<'a, 'b> =
    delegate of Recursive<'a, 'b> -> ('a -> 'b)

let z f =
    (Recursive(fun g -> f (fun x -> g.Invoke g x))).Invoke
        (Recursive(fun g -> f (fun x -> g.Invoke g x)))

let fib40 =
    z (fun fib f1 f2 n -> if n = 0 then f1 else fib f2 (f1 + f2) (n - 1)) 0 1 40

printfn "%d" fib40
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Microsoft] Angularチュートリアル Tour of Heroes を Blazor で再実装する

Angularのチュートリアル: Tour of Heroes をBlazorを使って再実装してみます。

Angularチュートリアル Tour of Heroes を Blazor で再実装する 目次

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 9.0 で範囲チェックがちょっと楽になる

int 型の変数 i0 以上 10 未満であることを判定するには次のようにします。

if (i >= 0 && i < 10)
{
}

C# 9.0 では次のようにも書けます。

if (i is >= 0 and < 10)
{
}

何が嬉しいかと言うと、i の評価が一回で済むところです。二番目のコードをよく見てください。i が一回しか書かれていませんよね?

評価が一回で済むということは、変数だけではなく、副作用を持つ関数などにも使えるということです。

if (GetSomeInt() is >= 0 and < 10)
{
}

GetSomeInt() が副作用を持つ場合、今までは評価(この場合は関数呼び出し)を一回で済ませるため、戻り値を一度変数に入れなければいけませんでした。しかしこれからはその必要はありません。

このコードは C# 9.0 でのパターンマッチングの進化によって可能になります。パターンマッチングと言えば switch 式ですが、それ以外にも if や三項演算子など bool が必要なところで使うことができます。

型パターン、位置パターン、プロパティパターンなど様々なパターン(将来はもっと予定されています)が論理演算できるようになるので、これからは条件式の形が大きく変わりそうです。「パターンマッチング実は少し苦手なんだけど」という人も、避けて通れなくなるのではないでしょうか。

参考: パターン マッチング - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 9.0 で条件式が革命を起こす

int 型の変数 i0 以上 10 未満であることを判定するには次のようにします。

if (i >= 0 && i < 10)
{
}

C# 9.0 では次のようにも書けます。

if (i is >= 0 and < 10)
{
}

何が嬉しいかと言うと、i の評価が一回で済むところです。二番目のコードをよく見てください。i が一回しか書かれていませんよね?

評価が一回で済むということは、変数だけではなく、副作用を持つ関数などにも使えるということです。

if (GetSomeInt() is >= 0 and < 10)
{
}

GetSomeInt() が副作用を持つ場合、今までは評価(この場合は関数呼び出し)を一回で済ませるため、戻り値を一度変数に入れなければいけませんでした。しかしこれからはその必要はありません。

ちなみに、i2 3 5 のいずれか、または 10 以上かどうかを判定するには次のようにします。

if (i is 2 or 3 or 5 or >= 10)
{
}

また、カッコや not を使ってもっと複雑な論理演算もできます。

if (i is (<= 0 or 3 or >= 10) and not 15)
{
}

これらのコードは C# 9.0 でのパターンマッチングの進化によって可能になります。パターンマッチングと言えば switch 式ですが、それ以外にも if や三項演算子など bool が必要なところで使うことができます。

型パターン、位置パターン、プロパティパターンなど様々なパターン(将来はもっと予定されています)が論理演算できるようになるので、これからは条件式の形が大きく変わりそうです。「パターンマッチング実は少し苦手なんだけど」という人も、避けて通れなくなるのではないでしょうか。

参考: パターン マッチング - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

パターンを組み合わせるとこんなこともできます。たとえば次のコードは object 型の変数を int と比較しているのでコンパイルエラーになります。

object o = 1;

if (o > 0)
{
}

これを次のようにパターンマッチングで書き換えるとコンパイルが通ります。この時、onull など int でない場合は if 文の中身は実行されないため、引数で渡された時などの null 判定を省略できます。

object o = 1;

if (o is > 0)
{
}

しかし o は依然 object なので次はコンパイルエラーになります。

object o = 1;

if (o is > 0)
{
    o += 2;
}

ここに型パターンを組み合わせます。

object o = 1;

if (o is int i and > 0)
{
    i += 2;
}

また、うるう年判定は次のように書けます。

bool IsLeapYear(int year) =>
    (year % 400, year % 100, year % 4) is (0, _, _) or (_, not 0, 0);

このようにタプルと位置パターンを組み合わせると、条件演算のネストがなくなってフラットに判定できることがあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む