Last active
February 19, 2018 13:24
-
-
Save htsign/f6cc6608cc2cc056704c8e12bda7114c to your computer and use it in GitHub Desktop.
引用式を評価して、式の途中で null になった場合に全体として null を返す関数を定義したい人生だった。C# でいうnull条件演算子( ?. ) のようなものを作りたいので少しずつ実装を進める。つもり。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace QuotationUtility | |
open System.Reflection | |
open Microsoft.FSharp.Linq.RuntimeHelpers | |
open Microsoft.FSharp.Quotations | |
open Microsoft.FSharp.Quotations.Patterns | |
module Option = | |
let toObj = function None -> null | Some x -> x | |
module Quotation = | |
type ExprInfo = Expr option * MemberInfo * Expr list | |
let evaluate (expr : Expr<'a> when 'a : null) : 'a = | |
let eval = LeafExpressionConverter.EvaluateQuotation | |
// 引用式の解析を行う | |
let rec analyzeExpr (acc : ExprInfo list) = function | |
| None -> acc | |
| Some expr -> | |
// インスタンスが存在する限り、再帰的に式を潜る | |
match expr with | |
| PropertyGet (parent, pi, args) -> | |
analyzeExpr ((parent, pi :> MemberInfo, args) :: acc) parent | |
| Call (parent, mi, args) -> | |
analyzeExpr ((parent, mi :> MemberInfo, args) :: acc) parent | |
| FieldGet (parent, fi) -> | |
analyzeExpr ((parent, fi :> MemberInfo, []) :: acc) parent | |
| NewObject (ci, args) -> | |
analyzeExpr ((None, ci :> MemberInfo, args) :: acc) None | |
// 定数が与えられた場合は MemberInfo を null にして invoke 内で直接返すことにしてお茶を濁す | |
| Value (value, _) -> (Some <@@ value @@>, null, []) :: [] | |
| Application (_, value) -> (Some value, null, []) :: [] | |
| Lambda (_, expr) -> analyzeExpr acc (Some expr) | |
| _ -> invalidArg "expr" (sprintf "invalid quotation: %A" expr) | |
let invoke (ins : obj) (mi : MemberInfo) (args : obj []) = | |
match mi with | |
// expr が Value or Application にマッチした場合に評価される | |
| null -> ins | |
| :? PropertyInfo as pi -> pi.GetValue(ins, args) | |
| :? MethodInfo as mi -> mi.Invoke(ins, args) | |
| :? FieldInfo as fi -> fi.GetValue ins | |
| :? ConstructorInfo as ci -> ci.Invoke args | |
| _ -> invalidArg "mi" "unsupported type" | |
let infos = analyzeExpr [] (Some (expr :> Expr)) | |
let rec exec = function | |
| [] -> failwith "invalid quotation" | |
| (ins, mi, args) :: xs -> | |
let ins = ins |> Option.map eval |> Option.toObj | |
let args = args |> List.map eval |> List.toArray | |
match invoke ins mi args, xs with | |
| null, _ -> null | |
| value, [] -> value | |
| ins, (_, mi, args) :: xs -> exec ((Some <@@ ins @@>, mi, args) :: xs) | |
exec infos :?> 'a |
Author
htsign
commented
Feb 16, 2018
•
テストしてないけど、インスタンスが存在することを前提に組んでいるので、たぶんスタティックメソッドを呼んだ時点で死ぬと思う。
上でやってることと変わらないけど、メソッドチェーンが長くなっても問題ない…はず。
open QuotationUtility
type C() =
member __.D = "This is a property 'D'"
type B() =
member __.C = C()
type A() =
member __.B1 = B()
member __.B2 = Unchecked.defaultof<B>
let result1 = Quotation.evaluate <@ A().B1.C.D @>
let result2 = Quotation.evaluate <@ A().B2.C.D @>
printfn "1 : [%A] / 2 : [%A]" result1 result2
(* 1 : ["This is property 'D'"] / 2 : [<null>] *)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment