![](/img/trans.png)
[英]F# Type Constraint Mismatch when using a constraint on a generic function
[英]Checking if an F# generic parameter has an equality or comparison constraint
是否可以在運行時確定Type
的泛型參數是否具有equality
或comparison
的特殊 F# 約束之一? 這些約束記錄在此處。
作為一個具體示例,給定type X<'y when 'y: equality> = { Y: 'y }
,我將如何確定'y
在typedefof<X<_>>
中具有equality
約束?
我試過使用一些反射 API,例如Type.GetGenericParameterConstraints
和Type.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.