LINQ のクエリ パターン

Visual Studio で C# のプロジェクト (ここではコンソール アプリケーションにしています) を作成して、
次のようなクエリ式を書いてみます。

Program.cs


class Program
{
    static void Main(string[] args)
    {
        var result =
            from x in new Monad<int>()
            select x;
    }
}

public class Monad<T>
{
}


Monad<T> はとりあえず空のジェネリック クラスです。
この状態でビルドすると、

「ソース型 ○○ のクエリ パターンの実装が見つかりませんでした。’Select’ が見つかりません。」

というコンパイラ エラーになります。

コンパイラ エラー CS1936

 

このクエリ パターンとは LINQ 標準クエリ演算子のことで、具体的には Select, Where, OrderBy メソッドなどがあります。
標準クエリ演算子のクエリ式構文に対応が記載されており、
例えば C# で select 句が有効になるようにするには、
IEnumerable<T> でいうところの Enumerable.Select メソッドに相当するメソッドが実装されている必要があります。

 

というわけで、例として Maybe モナドとそのクエリ パターンを実装してみます。

Maybe.cs


public struct Maybe<T>
{
    public static readonly Maybe<T> None = new Maybe<T>();

    T _value;

    public T Value
    {
        get
        {
            if (!HasValue) throw new InvalidOperationException();
            return _value;
        }
    }

    public bool HasValue { get; private set; }

    public Maybe(T value)
        : this()
    {
        _value = value;
        HasValue = true;
    }

    public static explicit operator T(Maybe<T> value)
    {
        return value.Value;
    }

    public static implicit operator Maybe<T>(T value)
    {
        return new Maybe<T>(value);
    }

    public Maybe<TResult> Bind<TResult>(Func<T, Maybe<TResult>> func)
    {
        return HasValue
            ? func(_value)
            : Maybe<TResult>.None;
    }

    public override string ToString()
    {
        return HasValue
            ? _value.ToString()
            : "";
    }
}

public static class Maybe
{
    public static Maybe<T> ToMaybe<T>(this T value)
    {
        return value;
    }

    public static Maybe<TResult> Select<T, TResult>(this Maybe<T> maybe, Func<T, TResult> selector)
    {
        return maybe.HasValue
            ? selector((T)maybe)
            : Maybe<TResult>.None;
    }

    public static Maybe<TResult> SelectMany<T, U, TResult>(this Maybe<T> maybe, Func<T, Maybe<U>> selector, Func<T, U, TResult> resultSelector)
    {
        var selected = maybe.Bind(selector);
        return selected.HasValue
            ? resultSelector((T)maybe, (U)selected)
            : Maybe<TResult>.None;
    }

    public static Maybe<T> Where<T>(this Maybe<T> maybe, Func<T, bool> predicate)
    {
        return maybe.HasValue && predicate((T)maybe)
            ? maybe
            : Maybe<T>.None;
    }
}


Select メソッドのほか、

  • 複数の from 句に相当する SelectMany メソッド
  • where 句に相当する Where メソッド

を実装しています。
このようにクエリ パターンを実装しておくと、次のようにクエリ式を書くことができます。

Program.cs


class Program
{
    static void Main(string[] args)
    {
        var result1 = Add(1, 2); // 3
        var result2 = Add(2, 1); // None
        var result3 = Add(1, Maybe<int>.None); // None
    }

    static Maybe<int> Add(Maybe<int> x, Maybe<int> y)
    {
        return
            from _x in x
            from _y in y
            where _x < _y
            select _x + _y;
    }
}


 

このようにして、IEnumerable<T> インターフェイス以外でもクエリ式を利用することができます。
ちなみに、IObservable<T> インターフェイスのクエリ式については、
Reactive Extensionsの概要と利用方法にコード例が載っています。

 

作成したサンプル
MonadConsole (GitHub)

参照
コンパイラ エラー CS1936
標準クエリ演算子の概要
select 句 (C# リファレンス)
Query Expression Pattern で Maybe monad を書く
モナドの驚異

コメントを残す