0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

『IQueryable の実装』をF#で

Last updated at Posted at 2025-03-08
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

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}"
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?