linqの原理を深く探究する--どのように自分の言語の中でlinqを実現するか
6581 ワード
穴は少し掘られていますが、最近はscalaにlinqを加えるつもりです.
スパークRDDやDataFrameで直接使うのはエイズではないでしょうか.
c#を何度か使ったことがありますが、linqはとても直感的で、このデザインが大好きです.でも今は忘れ物が少ないので、linqはいったい何なのか振り返ってみましょう.
overview
タイプ署名の拡張方法の詳細はさておき、公式サイトから与えられた最も基本的な例から、linqが何なのかを全体的に概説します.
linq、言語統合クエリーは、文法的にクラスsqlをサポートするクエリー構文であり、sqlクエリーを熟知している広範なcoderに対して、チェーンメソッド呼び出しよりも可読性が高く、どこへ行ったのか分からない.
しかし、これは文法糖にすぎず、コンパイル後も方法呼び出しに変換しなければならない.
たとえば、上記のクエリは次のようなものです.
もちろんselectのデータは変わらないので、このselect呼び出しは完全に省略できます.Whereはfilterに相当し、Selectはmapに相当し、これらの簡単な操作は非常に理解しやすい.linqでサポートされている他のjoin、aggerateなどのオペレータは、同じように書く方法で、理解しやすいです.
特殊なのは、複数のfromがつながっていると、少し複雑になります.このような状況を具体的に紹介します.
fromとSelectMany
まず例をあげます.
クエリー文だけを見て、numbersとnumbers(自分と自分)のデカルト積の中で2つの数の和が11より大きい組み合わせを選択し、出力は:
少し拡張した内容を話して、読めなくても大丈夫です.スキップしてもいいです.興味があればhaskellのMonadを理解してください.
scalaのコードを見ています.
出力結果:
次にghciで実行できるhaskellコードです.
haskellはforeachがなくて、手動で結果を見てみます
に届く
結果がそっくりであることがわかりますが、実はこの3つはもともと同じことです.c#直列のfromクエリー式、scalaのfor文法、haskellのdo notationは、本質的に一つのものであり、文法糖であり、Monadの操作を直列に接続し、最後に方法/関数呼び出しに翻訳される.その下位メソッド/関数は、それぞれ
本題に戻り、これらの容器を配列するSelectMany法の具体的な機能を見てみましょう.
関数を受け入れるSelectMany
まず、最も一般的なSelectManyの例を見てみましょう.これも本来の意味です.
出力:
簡単に理解できますが、SelectManyはコンテナの各要素に1回適用し、毎回新しいコンテナを返します.例えば、
この関数の署名を見てみましょう.
例のタイプに対応しています.
c#に触れたことがない場合は2点に注意してください.の最初のパラメータ
このSelectManyは何の役に立ちますか?
前述したように、一連の操作を直列に接続することができます.もう1つの例を見て、2つのリストのデカルト積を求めます.例えば、
出力:
次に、3つのリストのデカルト積を求めます.
2つの関数を受け入れるSelectMany
まず例を見てみましょう.
結果:
翻訳ルール
では、メソッド呼び出し形式を書きます.
スパークRDDやDataFrameで直接使うのはエイズではないでしょうか.
c#を何度か使ったことがありますが、linqはとても直感的で、このデザインが大好きです.でも今は忘れ物が少ないので、linqはいったい何なのか振り返ってみましょう.
overview
タイプ署名の拡張方法の詳細はさておき、公式サイトから与えられた最も基本的な例から、linqが何なのかを全体的に概説します.
using System;
using System.Linq;
class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}
linq、言語統合クエリーは、文法的にクラスsqlをサポートするクエリー構文であり、sqlクエリーを熟知している広範なcoderに対して、チェーンメソッド呼び出しよりも可読性が高く、どこへ行ったのか分からない.
しかし、これは文法糖にすぎず、コンパイル後も方法呼び出しに変換しなければならない.
たとえば、上記のクエリは次のようなものです.
var numQuery = numbers.Where(num => num %2 ==0).Select(num => num);
もちろんselectのデータは変わらないので、このselect呼び出しは完全に省略できます.Whereはfilterに相当し、Selectはmapに相当し、これらの簡単な操作は非常に理解しやすい.linqでサポートされている他のjoin、aggerateなどのオペレータは、同じように書く方法で、理解しやすいです.
特殊なのは、複数のfromがつながっていると、少し複雑になります.このような状況を具体的に紹介します.
fromとSelectMany
まず例をあげます.
using System;
using System.Linq;
class IntroToLINQ
{
static void Main ()
{
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
var numQuery =
from num0 in numbers
from num1 in numbers
where num0 + num1 > 11
select new {num0, num1};
foreach (var num in numQuery) {
Console.Write ("{0,1} ", num);
}
}
}
クエリー文だけを見て、numbersとnumbers(自分と自分)のデカルト積の中で2つの数の和が11より大きい組み合わせを選択し、出力は:
{ num0 = 5, num1 = 6 }
{ num0 = 6, num1 = 5 }
{ num0 = 6, num1 = 6 }
少し拡張した内容を話して、読めなくても大丈夫です.スキップしてもいいです.興味があればhaskellのMonadを理解してください.
scalaのコードを見ています.
object Main {
def main(args: Array[String]): Unit = {
val numbers = List(0, 1, 2, 3, 4, 5, 6)
val queryResult =
for (num0 10) yield (num0, num1)
queryResult.foreach(println)
}
}
出力結果:
(5,6)
(6,5)
(6,6)
次にghciで実行できるhaskellコードです.
numbers = [0, 1, 2, 3, 4, 5, 6]
queryResult = do {
num010
then [(num0,num1)]
else []
}
haskellはforeachがなくて、手動で結果を見てみます
> queryResult
に届く
=> [(5,6),(6,5),(6,6)]
結果がそっくりであることがわかりますが、実はこの3つはもともと同じことです.c#直列のfromクエリー式、scalaのfor文法、haskellのdo notationは、本質的に一つのものであり、文法糖であり、Monadの操作を直列に接続し、最後に方法/関数呼び出しに翻訳される.その下位メソッド/関数は、それぞれ
SelectMany
、flatMap
、>>=
である.Monadは何なのか、また別の話題ですが、大きくないと言っても小さくない話題です...本題に戻り、これらの容器を配列するSelectMany法の具体的な機能を見てみましょう.
関数を受け入れるSelectMany
まず、最も一般的なSelectManyの例を見てみましょう.これも本来の意味です.
int[] list = { 1, 2, 3 };
Func selector = x =>
{
var s = String.Format("hi{0}", x);
return new[] { s, s, s };
};
var result = list.SelectMany(selector);
foreach (var e in result)
{
Console.Write("{0},", e);
}
出力:
hi1,hi1,hi1,hi2,hi2,hi2,hi3,hi3,hi3,
簡単に理解できますが、SelectManyはコンテナの各要素に1回適用し、毎回新しいコンテナを返します.例えば、
selector
は数字N
を受け入れて{"hiN","hiN","hiN"}
というリストを返し、要素ごとに呼び出すとこの大きなリスト{{"hi1","hi1","hi1"},{"hi2","hi2","hi2"},{"hi3","hi3","hi3"}}
が得られ、最後にこの大きなリストを平らにすると最終的な結果が得られる:{hi1,hi1,hi1,hi2,hi2,hi2,hi3,hi3,hi3}
.もちろん実際の実現は必ずしもそうではないが、このように理解すれば正しい.この関数の署名を見てみましょう.
public static IEnumerable SelectMany(
this IEnumerable source,
Func> selector
);
例のタイプに対応しています.
c#に触れたことがない場合は2点に注意してください.
source
の前にthis
があります.これは拡張方法の構文であり、source.SelectMany(selector)
はSelectMany(source,selector)
に相当します.IEnumerable
はインタフェースであり、c#の配列はこのインタフェースを実現している.したがって、int[]
はIEnumerable
string[]
を満たし、IEnumerable
を満たす.このSelectManyは何の役に立ちますか?
前述したように、一連の操作を直列に接続することができます.もう1つの例を見て、2つのリストのデカルト積を求めます.例えば、
[1,2]
he [3,4]
に対して、[(1,3),(1,4),(2,3),(2,4)]
を得るにはどうすればいいですか.int[] alist = { 1, 2 };
int[] blist = { 3, 4 };
var result = alist.SelectMany(
a => blist.Select(
b => new { a, b }
)
);
foreach (var e in result)
{
Console.Write("{0},", e);
}
出力:
{ a = 1, b = 3 },{ a = 1, b = 4 },{ a = 2, b = 3 },{ a = 2, b = 4 },
どのように理解して、中の関数の機能を見てもいいです.alistの1つの要素aに対して、blistの各要素と組み合わせて最後に[(a,3),(a,4)]を生成します.alistのaごとにこの関数を1回来ると[ [(a0,3),(a0,4)], [(a1,3),(a1,4)] ]
が得られ、それを平らにするのが最後の結果です.次に、3つのリストのデカルト積を求めます.
int[] alist = { 1, 2 };
int[] blist = { 3, 4 };
int[] clist = { 4, 5 };
var result = alist.SelectMany(
a => blist.SelectMany(
b => clist.Select(
c => new { a, b, c }
)
)
);
foreach (var e in result)
{
Console.Write("{0},", e);
}
{ a = 1, b = 3, c = 4 },{ a = 1, b = 3, c = 5 },{ a = 1, b = 4, c = 4 },{ a = 1, b = 4, c = 5 },{ a = 2, b = 3, c = 4 },{ a = 2, b = 3, c = 5 },{ a = 2, b = 4, c = 4 },{ a = 2, b = 4, c = 5 },
2つの関数を受け入れるSelectMany
まず例を見てみましょう.
int[] ilist = { 1, 2 };
double[] dlist = { 0.1, 0.2, 0.3 };
Func collectionSelector =
_ => dlist;
Func resultSelector =
(int_num, double_num) => String.Format("'{0}'", int_num + double_num);
var result = ilist.SelectMany(collectionSelector, resultSelector);
foreach (var e in result)
{
Console.Write("{0},", e);
}
結果:
'1.1','1.2','1.3','2.1','2.2','2.3',
翻訳ルール
では、メソッド呼び出し形式を書きます.
numQuery1 = numbers.SelectMany (
_ => numbers,
(num0, num1) => new {num0, num1}
).Where( x => x.num0 + x.num1 >11);