[英]Checking if an F# generic parameter has an equality or comparison constraint
Is it possible to determine at run-time if a generic parameter to a Type
has one of the special F# constraints of equality
or comparison
?是否可以在运行时确定
Type
的泛型参数是否具有equality
或comparison
的特殊 F# 约束之一? These constraints are documented here .这些约束记录在此处。
As a concrete example, given type X<'y when 'y: equality> = { Y: 'y }
, how would I determine that 'y
has the equality
constraint in typedefof<X<_>>
?作为一个具体示例,给定
type X<'y when 'y: equality> = { Y: 'y }
,我将如何确定'y
在typedefof<X<_>>
中具有equality
约束?
I've tried using a few of the reflection APIs like Type.GetGenericParameterConstraints
and Type.GenericParameterAttributes
but both are empty.我试过使用一些反射 API,例如
Type.GetGenericParameterConstraints
和Type.GenericParameterAttributes
,但两者都是空的。
This question mentions that the F# PowerPack can be used like so:这个问题提到 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))
However, this library does not appear to support .NET core and has since been split up and can now be found here .然而,这个库似乎不支持 .NET 核心,并且已经被拆分,现在可以在这里找到。 The GitHub page mentions that "F# metadata reader is replaced by FSharp.Compiler.Service", but on a brief examination of
FSharp.Compiler.Service
this API appears to be much more complex to set up and use than the example above. GitHub 页面提到“F# metadata reader is replaced by FSharp.Compiler.Service”,但在
FSharp.Compiler.Service
的简要检查中,这个 API 的设置和使用似乎比上面的示例复杂得多。
Is there a simple way to access these special constraints in F# 6 / .NET 6 using the reflection API or using some other metadata reader?有没有一种简单的方法可以使用反射 API 或使用其他元数据读取器访问 F# 6 / .NET 6 中的这些特殊约束?
Currently I'm working around the issue by manually annotating the parameter using an attribute:目前我正在通过使用属性手动注释参数来解决这个问题:
[<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>()
Obviously having to manually annotate is not ideal, though!但是,显然必须手动注释并不理想!
Rather than pull in the whole FSharp.Compiler.Service
package, I think it is easier to re-implement the logic defined in the spec :与其引入整个
FSharp.Compiler.Service
package,我认为重新实现规范中定义的逻辑更容易:
The constraint
type: comparison
is a comparison constraint.约束
type: comparison
是比较约束。 Such a constraint is met if all the following conditions hold:如果满足以下所有条件,则满足此类约束:
If the type is a named type, then the type definition does not have, and is not inferred to have, the
NoComparison
attribute, and the type definition implementsSystem.IComparable
or is an array type or isSystem.IntPtr
or isSystem.UIntPtr
.如果类型是命名类型,则类型定义不具有且不被推断具有
NoComparison
属性,并且类型定义实现System.IComparable
或者是数组类型或者是System.IntPtr
或者是System.UIntPtr
.If the type has comparison dependencies
ty1
, ...,tyn
, then each of these must satisfytyi: comparison
如果类型具有比较依赖性
ty1
, ...,tyn
,那么它们中的每一个都必须满足tyi: comparison
This code works for the test cases below, and I think it would be fairly easy to tweak if more edge cases are discovered.这段代码适用于下面的测试用例,我认为如果发现更多的边缘用例,调整起来会相当容易。
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.