Visual Studio で C# のプロジェクト (ここではコンソール アプリケーションにしています) を作成して、
次のようなクエリ式を書いてみます。
Program.cs
{
static void Main(string[] args)
{
var result =
from x in new Monad<int>()
select x;
}
}
public class Monad<T>
{
}
Monad<T> はとりあえず空のジェネリック クラスです。
この状態でビルドすると、
「ソース型 ○○ のクエリ パターンの実装が見つかりませんでした。’Select’ が見つかりません。」
というコンパイラ エラーになります。
このクエリ パターンとは LINQ 標準クエリ演算子のことで、具体的には Select, Where, OrderBy メソッドなどがあります。
標準クエリ演算子のクエリ式構文に対応が記載されており、
例えば C# で select 句が有効になるようにするには、
IEnumerable<T> でいうところの Enumerable.Select メソッドに相当するメソッドが実装されている必要があります。
というわけで、例として Maybe モナドとそのクエリ パターンを実装してみます。
Maybe.cs
{
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
{
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 を書く
モナドの驚異
コメントを残す