編集:注、このwall'o'text grinに置き換えるために他の回答を削除しました
NINJAEDIT: 楽しい事実: Powershell (@Loudenvier の回答で言及) はかつて「モナド」と名付けられる予定でした - また、トピックに関する Wes Dyer のブログ投稿を見つけました: The Marvels of Monads
この「モナド」全体を見る非常に単純な方法の 1 つは、非常に基本的なインターフェースを備えたボックスとして考えることです。
使用法も同様に概念的に単純です - 「もの」があるとしましょう:
- 「物」を箱に入れ(これは「返品」になります)、「BoxOfThing」を持つことができます
- この箱から物を取り出して別の箱に入れる方法を指示できます(バインド)
- 空のボックスを取得できます (「ゼロ」: 1 を掛けたり、ゼロを追加したりするような、一種の「操作なし」と考えてください)。
- (他にもルールはありますが、この3つが一番面白いです)
バインド ビットは非常に興味深い部分であり、ほとんどの人の頭を爆発させる部分でもあります。基本的に、あなたはボックスを連鎖させる方法についてある種の仕様を与えています: "Option" または "Maybe" というかなり単純なモナドを見てみましょう - 少し似てNullable<T>
いますが、よりクールです。
したがって、誰もがどこでも null をチェックすることを嫌いますが、参照型が機能する方法のために強制されています。私たちが望んでいるの
は、次のようなコードを作成できることです。
var zipcodesNearby = order.Customer.Address.City.ZipCodes;
そして、(顧客が有効である + アドレスが有効である + ...) 場合は有効な回答を返すか、そのロジックのいずれかのビットが失敗した場合は「何もありません」...しかし、いいえ、次のことを行う必要があります。
List<string> zipcodesNearBy = new List<string>();
if(goodOrder.Customer != null)
{
if(goodOrder.Customer.Address != null)
{
if(goodOrder.Customer.Address.City != null)
{
if(goodOrder.Customer.Address.City.ZipCodes != null)
{
zipcodesNearBy = goodOrder.Customer.Address.City.ZipCodes;
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
}
else { /* do something else? throw? */ }
(注:該当する場合は、ヌル合体に頼ることもできます-見た目はかなり悪いですが)
List<string> nullCoalescingZips =
((((goodOrder ?? new Order())
.Customer ?? new Person())
.Address ?? new Address())
.City ?? new City())
.ZipCodes ?? new List<string>();
Maybe モナドの「ルール」は次のようになります。
(注: C# はこのタイプの型マングリングには理想的ではないため、少し不安定になります)
public static Maybe<T> Return(T value)
{
return ReferenceEquals(value, null) ? Maybe<T>.Nothing : new Maybe<T>() { Value = value };
}
public static Maybe<U> Bind<U>(Maybe<T> me, Func<T, Maybe<U>> map)
{
return me != Maybe<T>.Nothing ?
// extract, map, and rebox
map(me.Value) :
// We have nothing, so we pass along nothing...
Maybe<U>.Nothing;
}
しかし、これはいくつかのNASTYコードにつながります:
var result1 =
Maybe<string>.Bind(Maybe<string>.Return("hello"), hello =>
Maybe<string>.Bind(Maybe<string>.Return((string)null), doh =>
Maybe<string>.Bind(Maybe<string>.Return("world"), world =>
hello + doh + world).Value
).Value
);
幸いなことに、きちんとしたショートカットがあります:SelectMany
これは "Bind" とほぼ同等です:
SelectMany
私たちのために実装するとMaybe<T>
...
public class Maybe<T>
{
public static readonly Maybe<T> Nothing = new Maybe<T>();
private Maybe() {}
public T Value { get; private set;}
public Maybe(T value) { Value = value; }
}
public static class MaybeExt
{
public static bool IsNothing<T>(this Maybe<T> me)
{
return me == Maybe<T>.Nothing;
}
public static Maybe<T> May<T>(this T value)
{
return ReferenceEquals(value, null) ? Maybe<T>.Nothing : new Maybe<T>(value);
}
// Note: this is basically just "Bind"
public static Maybe<U> SelectMany<T,U>(this Maybe<T> me, Func<T, Maybe<U>> map)
{
return me != Maybe<T>.Nothing ?
// extract, map, and rebox
map(me.Value) :
// We have nothing, so we pass along nothing...
Maybe<U>.Nothing;
}
// This overload is the one that "turns on" query comprehension syntax...
public static Maybe<V> SelectMany<T,U,V>(this Maybe<T> me, Func<T, Maybe<U>> map, Func<T,U,V> selector)
{
return me.SelectMany(x => map(x).SelectMany(y => selector(x,y).May()));
}
}
これで、LINQ 内包構文に便乗できるようになりました。
var result1 =
from hello in "Hello".May()
from oops in ((string)null).May()
from world in "world".May()
select hello + oops + world;
// prints "Was Nothing!"
Console.WriteLine(result1.IsNothing() ? "Was Nothing!" : result1.Value);
var result2 =
from hello in "Hello".May()
from space in " ".May()
from world in "world".May()
select hello + space + world;
// prints "Hello world"
Console.WriteLine(result2.IsNothing() ? "Was Nothing!" : result2.Value);
var goodOrder = new Order { Customer = new Person { Address = new Address { City = new City { ZipCodes = new List<string>{"90210"}}}}};
var badOrder = new Order { Customer = new Person { Address = null }};
var zipcodesNearby =
from ord in goodOrder.May()
from cust in ord.Customer.May()
from add in cust.Address.May()
from city in add.City.May()
from zip in city.ZipCodes.May()
select zip;
// prints "90210"
Console.WriteLine(zipcodesNearby.IsNothing() ? "Nothing!" : zipcodesNearby.Value.FirstOrDefault());
var badZipcodesNearby =
from ord in badOrder.May()
from cust in ord.Customer.May()
from add in cust.Address.May()
from city in add.City.May()
from zip in city.ZipCodes.May()
select zip;
// prints "Nothing!"
Console.WriteLine(badZipcodesNearby.IsNothing() ? "Nothing!" : badZipcodesNearby.Value.FirstOrDefault());
はは、これの要点全体を言及するのを忘れていたことに気付きました...つまり、基本的に、パイプラインの各段階で「バインド」に相当するものが何であるかを理解したら、同じタイプのシュードモナディック コードを使用して各型変換のラッピング、アンラッピング、および処理を処理します。