『IQueryable の実装』をF#で

- MSDN Blogs: [LINQ: Building an IQueryable provider series - The Wayward WebLog](https://web.archive.org/web/20100911095114/http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx)
- ufcppさんの記事: [[雑記] IQueryable の実装](https://ufcpp.net/study/csharp/sp3_iqueryable.html)

```fsharp:Linq.fs
module My.Linq

open System
open System.Collections.Generic
open System.Linq
open System.Linq.Expressions
open System.Reflection

// 条件に一致する最初の結果を返す関数
let rec tryFirst (funcs: ('a -> 'b option) list) (input: 'a) =
    match funcs with
    | [] -> None
    | f :: rest ->
        match f input with
        | Some result -> Some result
        | None -> tryFirst rest input

// FindIEnumerable関数の実装
let rec findIEnumerable (seqType: Type) : Type option =
    // チェック関数のリスト
    let checks =
        [
          // null または string型のチェック
          fun (t: Type) -> if t = null || t = typeof<string> then Some null else None

          // 配列型のチェック
          fun (t: Type) ->
              if t.IsArray then
                  Some(typeof<IEnumerable<_>>.MakeGenericType(t.GetElementType()))
              else
                  None

          // ジェネリック型のチェック
          fun (t: Type) ->
              if t.IsGenericType then
                  t.GetGenericArguments()
                  |> Array.tryPick (fun arg ->
                      let ienum = typeof<IEnumerable<_>>.MakeGenericType(arg)
                      if ienum.IsAssignableFrom(t) then Some ienum else None)
              else
                  None

          // インターフェースのチェック
          fun (t: Type) ->
              let ifaces = t.GetInterfaces()

              if ifaces <> null && ifaces.Length > 0 then
                  ifaces |> Array.tryPick findIEnumerable
              else
                  None

          // 基底クラスのチェック
          fun (t: Type) ->
              if t.BaseType <> null && t.BaseType <> typeof<obj> then
                  findIEnumerable t.BaseType
              else
                  None ]

    // すべてのチェックを順番に試す
    tryFirst checks seqType

// GetElementType関数の実装
let getElementType (seqType: Type) : Type =
    match findIEnumerable seqType with
    | None -> seqType
    | Some null -> seqType // nullの場合も元の型を返す
    | Some ienum -> ienum.GetGenericArguments().[0]

[<AbstractClass>]
type QueryProvider() =
    abstract member GetQueryText: Expression -> string
    abstract member Execute: Expression -> obj

    interface IQueryProvider with
        member this.CreateQuery(expression: Expression) : IQueryable =
            let elementType = getElementType expression.Type

            try
                let t = typeof<Query<_>>.MakeGenericType(elementType)
                let args: obj array = [| this; expression |]
                Activator.CreateInstance(t, args) :?> IQueryable
            with :? TargetInvocationException as ex ->
                raise ex.InnerException

        member this.CreateQuery<'TElement>(expression: Expression) : IQueryable<'TElement> =
            Query<'TElement>(this, expression)

        member this.Execute(expression: Expression) : obj = this.Execute expression
        member this.Execute<'TResult>(expression: Expression) : 'TResult = this.Execute expression :?> 'TResult

and Query<'T> =
    val private _provider: QueryProvider
    val mutable private _expression: Expression

    static member private check(expression: Expression) =
        if typeof<IQueryable<'T>>.IsAssignableFrom(expression.Type) then
            expression
        else
            raise (ArgumentOutOfRangeException("expression"))

    member this.Provider = this._provider
    member this.Expression = this._expression

    new(provider: QueryProvider, expression: Expression) =
        { _provider = provider
          _expression = Query<'T>.check expression }

    new(provider: QueryProvider) as this =
        { _provider = provider
          _expression = Expression.Empty() }

        then this._expression <- Query<'T>.check (Expression.Constant(this))

    override this.ToString() =
        this._provider.GetQueryText(this._expression)

    interface Collections.IEnumerable with
        member this.GetEnumerator() : Collections.IEnumerator =
            (this :> IEnumerable<'T>).GetEnumerator()

    interface IQueryable<'T> with
        member this.ElementType = typeof<'T>
        member this.Expression = this._expression
        member this.Provider = this._provider

        member this.GetEnumerator() =
            (this.Provider.Execute(this.Expression) :?> _ seq).GetEnumerator()

    interface IOrderedQueryable<'T> with

```

```fsharp:Program.fs
open System
open System.Linq
open System.Linq.Expressions
open Microsoft.FSharp.Linq.RuntimeHelpers
open My.Linq

type TestProvider() =
    inherit QueryProvider()
    override _.GetQueryText(exp: Expression) = ""
    override _.Execute(exp: Expression) = null
    static member CreateQueryable<'T>() = Query<'T>(TestProvider())

let q1 = TestProvider.CreateQueryable<int>()
printfn $"q1: %A{q1.Expression}"

let q2 = q1.Where(fun x -> x > 10)
printfn $"q2: %A{q2.Expression}"

let q3 = q2.OrderBy(fun x -> x)
printfn $"q3: %A{q3.Expression}"

let q4 = q3.Select(fun x -> x * x)
printfn $"q4: %A{q4.Expression}"

let q5 =
    query {
        for x in TestProvider.CreateQueryable<int>() do
            where (x > 10)
            sortBy x
            select (x * x)
    }

printfn $"q5: %A{q5.Expression}"

let expr = <@ Func<int, bool>(fun x -> x > 10) @>
let linqExpr = LeafExpressionConverter.QuotationToLambdaExpression(expr)
let q2' = q1.Where(linqExpr)
printfn $"q2': %A{q2'.Expression}"
```