簡體   English   中英

檢查 F# 通用參數是否具有相等或比較約束

[英]Checking if an F# generic parameter has an equality or comparison constraint

是否可以在運行時確定Type的泛型參數是否具有equalitycomparison的特殊 F# 約束之一? 這些約束記錄在此處

作為一個具體示例,給定type X<'y when 'y: equality> = { Y: 'y } ,我將如何確定'ytypedefof<X<_>>中具有equality約束?

我試過使用一些反射 API,例如Type.GetGenericParameterConstraintsType.GenericParameterAttributes ,但兩者都是空的。

這個問題提到 F# PowerPack 可以像這樣使用:

open Microsoft.FSharp.Metadata

let setEntity = FSharpAssembly.FSharpLibrary.GetEntity("Microsoft.FSharp.Collections.FSharpSet`1")
for typeArg in setEntity.GenericParameters do
  printfn "%s - comparison=%b" 
    typeArg.Name 
    (typeArg.Constraints |> Seq.exists (fun c -> c.IsComparisonConstraint))

然而,這個庫似乎不支持 .NET 核心,並且已經被拆分,現在可以在這里找到。 GitHub 頁面提到“F# metadata reader is replaced by FSharp.Compiler.Service”,但在FSharp.Compiler.Service的簡要檢查中,這個 API 的設置和使用似乎比上面的示例復雜得多。

有沒有一種簡單的方法可以使用反射 API 或使用其他元數據讀取器訪問 F# 6 / .NET 6 中的這些特殊約束?

目前我正在通過使用屬性手動注釋參數來解決這個問題:

[<AttributeUsage(validOn = AttributeTargets.GenericParameter, AllowMultiple = false)>]
type ConstraintAttribute([<ParamArray>] constraints: string []) =
    inherit Attribute()
    member _.Constraints = constraints |> List.ofArray

type X<[<Constraint("equality")>] 'y when 'y: equality> = { Y: 'y }

typedefof<X<_>>.GetGenericArguments().[0].GetCustomAttributes<ConstraintAttribute>()

但是,顯然必須手動注釋並不理想!

與其引入整個FSharp.Compiler.Service package,我認為重新實現規范中定義的邏輯更容易:

約束type: comparison是比較約束。 如果滿足以下所有條件,則滿足此類約束:

  • 如果類型是命名類型,則類型定義不具有且不被推斷具有NoComparison屬性,並且類型定義實現System.IComparable或者是數組類型或者是System.IntPtr或者是System.UIntPtr .

  • 如果類型具有比較依賴性ty1 , ..., tyn ,那么它們中的每一個都必須滿足tyi: comparison

這段代碼適用於下面的測試用例,我認為如果發現更多的邊緣用例,調整起來會相當容易。

open System
open FSharp.Reflection

let comparisonDeps (t : Type) =
  seq {
    if FSharpType.IsTuple t then
      yield! FSharpType.GetTupleElements t

    elif FSharpType.IsUnion t then
      for uci in FSharpType.GetUnionCases(t, true) do
        for f in uci.GetFields() do
          f.PropertyType

    elif FSharpType.IsRecord t then
      for rf in FSharpType.GetRecordFields t do
        rf.PropertyType
  }

let isComparisonType (t : Type) =
  let seen = System.Collections.Generic.HashSet<Type>()

  let rec loop t =
    seen.Add(t) |> ignore
    (
      t = typeof<IntPtr>
    )
    || (
      t = typeof<UIntPtr>
    )
    || (
      t.IsArray
    )
    || (
      (
        t.GetInterfaces()
        |> Seq.contains typeof<System.IComparable>
      )
      && (
        comparisonDeps t
        |> Seq.filter (fun t -> not (seen.Contains(t)))
        |> Seq.forall loop
      )
    )

  loop t




#r "nuget: Expecto, 9.0.4"

open Expecto

let makeTest candidate expected =
  let t = candidate.GetType()

  let message =
    if expected then
      $"%A{candidate} is a comparison type"
    else
      $"%A{candidate} is not a comparison type"

  test $"%s{t.Name} (e.g. %A{candidate})" {
    let actual = isComparisonType t

    Expect.equal actual expected message
  }

type Foo<'t> =
  {
    Foo : 't
  }

[<NoComparison>]
type Bar<'t> =
  {
    Bar : 't
  }

[<NoComparison>]
type Qux =
  {
    Qux : int
  }

type Baz =
  {
    Baz : Qux
  }

let tests =
  testList "isComparisonType" [
    makeTest 123 true
    makeTest "abc" true
    makeTest [ 1; 2; 3 ] true
    makeTest (1, "a") true
    makeTest (Set.ofSeq [ 1; 2; 3 ]) true
    makeTest { Foo = 123 } true
    makeTest (Some { Foo = 123 }) true
    makeTest { Foo = fun x -> x + 1 } false
    makeTest { Bar = "abc" } false
    makeTest [ { Bar = 7 } ] false
    makeTest { Foo = { Bar = "abc" } } false
    makeTest { Qux = 2 } false
    makeTest (true, { Qux = 2 }) false
    makeTest (Map.ofSeq [ 1, { Qux = 2 } ]) true
    makeTest (Some { Qux = 2 }) false
    makeTest { Baz = { Qux = 2 } } false
    makeTest [| true; false |] true
    makeTest (IntPtr 1) true
    makeTest [| IntPtr 1; IntPtr 2; IntPtr 3 |] true
    makeTest (UIntPtr 42u) true
  ]

let args = fsi.CommandLineArgs |> Array.skip 1

runTestsWithCLIArgs [] args tests

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM