我有一個簡單的Either型別:
public class Left< L, R> : Either<L, R>
{
public override string ToString()
{
return Left.ToString()。
}
public Left(L left)
{
左 = 左。
IsLeft = true。
}
}
public class Right<L, R> : Either<L, R>
{
public override string ToString()
{
return Right.ToString()。
}
public Right(R right)。
{
右 = 右。
IsRight = true。
}
}
public class Either<L, R>
{
public bool IsLeft {get; protected set; }
public bool IsRight {get; protected set; }
public L Left {get; protected set; }
public R Right {get; protected set; }
public Either(L left)
{
左 = 左。
IsLeft = true。
}
public Either(R right)
{
右 = 右。
IsRight = true。
}
public Either()
{
}
我想用LINQ查詢語法來使用這個型別。我可以實作Select,但是我不太明白需要的SelectMany方法簽名。更準確地說,這樣做是為了:
var l = new Either< string, int>("no, this is a left") 。
var r = new Either<string, int> (2)。
var ret5 =
from x in l
from y in r
select y;
///將ret5作為一個Left回傳,預期資訊。
如果我使用下面的植入方式:
public static Either< L, RR> SelectMany< L, R, RR>(
this Either<L, R> either, // the source " collection"
Func<R, Either<L, R>> g, // 這里為什么是R? 為什么不是 L, R. 只能是1個引數。Func<TSource, IEnumerable<TCollection>> collectionSelector。
Func<L, R, RR>f //一個轉換,將包含的專案和轉換為轉換后的權利
)
{
if (ither.IsLeft) return new Left<L, RR>(ither.Left)。
var inner = g(ither.Right);
if (inner.IsLeft) return new Left<L, RR>(inner.Left)。
return new Right<L, RR>(f(inner.Left, inner.Right) )。
}
但是我無法在頭腦中把它與檔案中的簽名聯系起來(這里來自Jon Skeet的Edulinq帖子)
public static IEnumerable< TResult> SelectMany< TSource, TCollection, TResult> (
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector。
Func<TSource, TCollection, TResult> resultSelector)
對于有多個通用引數的類,是否有一個通用的方法來理解你的SelectMany方法的簽名?
編輯:LaYumba庫有一個類似的Transition示例類的簽名,所以......整套的SelectMany多載是否在某個地方被記錄下來--包括那些多引數型別?
uj5u.com熱心網友回復:
建議的SelectMany多載只能編譯,因為唯一的測驗案例并沒有改變型別。就是說,在
var ret5 =
from x in l
from y in r
select y。
l和r都有相同的型別。Either<string, int>。因此,雖然太受限制,但建議的SelectMany多載實際上足以使代碼得到編譯。然而,它不夠靈活,無法在不同型別的 Eithers 之間進行映射。
TL;DR;
。SelectMany的正確型別是:
public static Either < L, RR> SelectMany< L, R, T, RR> (
this Either<L, R> source,
Func<R, Either<L, T> > k,
Func<R, T, RR> s)
暫時忘記這個特殊的多載可能會有幫助,因為它是C#特有的怪異現象,在我所知道的其他語言(F#和Haskell)中沒有對應的多載。
系結
C#中的標準SelectMany方法對應于通常所說的monadic bind。對于OP中的Either型別,它看起來是這樣的:
public static Either< L, RR> SelectMany< L, R, RR>(
this Either<L, R> source,
Func<R, Either<L, RR>> selector)。
{
if (source.IsLeft)
return new Left<L, RR>(Source.Left);
return selector(source.Right)。
}
這個多載更容易處理,因為它不需要其他多載所需要的那個奇怪的額外步驟函式。
但要明確的是,另一個多載需要使查詢語法亮起,所以我將在后面回到這個問題。
如果你能實作一個像這樣的 SelectMany 方法,那么這個型別就形成了一個單體。請注意,它只映射了右邊的內容。如果我們也添加一個Select方法,這可能會更容易看到。
單體
所有的單體也是向量。如果你有一個合法的SelectMany(單體系結),你可以always實作Select,而且實作完全是可自動的:
public static Either< L, RR> Select< L, R, RR> (
this Either<L, R> source,
Func<R, RR> 選擇器)。
{
return source.SelectMany(r => new Right<L, RR>(selector(r) ))。
}
注意,Select只將R映射到RR,而L保持固定。Either實際上不只是一個單一的向量,而是一個向量家族--每個L都有一個。例如,Either<string, R>產生了一個與Either<int, R>不同的向量。然而,無論是哪一種,都是一個bifunctor。
只有一種型別可以映射。R.
查詢句法
如果你只需要方法呼叫語法,這兩個方法就足夠了,但是如果你還需要查詢語法,你就需要另一個SelectMany多載。據我所知,如果你已經有了Select和SelectMany,你總是可以(?)用同樣的嵌套lambda表達方式實作另一個多載:
public static Either< L, RR> SelectMany< L, R, T, RR> (
this Either<L, R> source,
Func<R, Either<L, T> > k,
Func<R, T, RR> s)。
{
return source.SelectMany(x => k(x).Select(y => s(x, y))。
}
我實際上是從一個完全不同的單體(State)的SelectMany方法中復制了這個運算式。
請再次注意,L是固定的。它沒有參與任何映射,所以就好像它不存在一樣。中間 "型別在這里被稱為T。
單態性
OP中建議的方法之所以能夠編譯,是因為C#的編譯器實際上是很寬容的。如果你讓泛型型別的泛型程度低于你必須的程度,它仍然可以編譯。例如,令我驚訝的是,我在去年發現,你也可以使用更有約束性的Select方法來實作單態函式。
封裝
綜上所述,您應該考慮為相關型別添加一些封裝。就目前的代碼而言,任何人都可以添加一個繼承自Either<L, R>的新類,并且表現得完全不正常。
例如,您可以轉而考慮一個Church-encoded Either。
uj5u.com熱心網友回復:
Func<R, Either<L, R>> g, // 為什么這里是R? 為什么不是L, R. 只能是1個引數。
因為在你的實作中,你是這樣做的:
g(either.right);
你可以很容易地把它寫成:
。Func<L, Either<L, R>> g。
并傳入:
g(ither.Left)。
當然,這表現得非常不同。 所以這只是一個你想要什么行為的問題。 你希望你的Either有這樣的行為:"如果外部的ither的值是左邊的,就使用它,甚至不構建內部的ither,如果外部的ither的值是右邊的,就獲得內部的值并使用它。 這意味著在任何時候你得到內部任一的值(也就是函式g)外部任一根據定義是一個右的任一。 如果它不是,你就已經回傳了左邊的值,甚至沒有呼叫g。
(我想借此機會指出,在這里使用有意義的變數名是問題的一部分。 當你只是呼叫一個變數g 沒有人知道這意味著什么,只是從名字上看。 如果你給它一個有意義的名字,它可能會讓你更清楚地知道它將在什么時候被使用,以及為什么在那種情況下從來沒有一個左邊的值可以傳遞給它。)
但是你可以寫一個有意義的名字。
但是你可以使用SelectMany撰寫一個不同的型別,它的行為是不同的。
本質上,問題歸結為在這段代碼中你希望y的型別是什么:
from x in l
from y in r
在你的例子中,你希望y是來自l的正確值。 但是你也可以很容易地寫一些其他的實作,傳入它想要的任何東西。 你可以傳入一個Tuple<L,R>并且讓查詢中的x代表一對both值。 現在對于你的特定的Either來說,左邊的值已知不會被填充,所以這實際上不是一個好主意,但是其他型別的一對值的單體可能需要both值來從先前的值中生成一個新的值。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/310072.html
標籤:
上一篇:具有多對多關系映射的.Net核心API和Automapper
下一篇:檢查二維串列中不同的特定字串
