[英]OR pattern with F# SRTP constraints is interpreted as AND
我想知道为什么|
,我的函数doThingsWithOrProps
中 SRTP 上下文中的“或”模式未按预期工作,也就是它应该能够接受具有属性PropA
或另一个属性PropB
的类型,但它被解释为 AND &
模式就像函数doThingsWithAndProps
一样,它实际上没有意义,因为相应两个函数的输入预期模式明显不同。
let inline (|PropA|) source =
(^Source: (member PropA: 'PropA) source)
let inline (|PropB|) source =
(^Source: (member PropB: 'PropB) source)
let inline (|PropAAndB|) (PropA (propA: 'PropA) & PropB (propB: 'PropB)) = (propA, propB)
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithAndProps (PropAAndB (propA: 'PropA, propB: 'PropB)) =
printfn $"({nameof propA} = %A{propA}, {nameof propB} = %A{propB})"
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
// Compiles just fine
doThingsWithAndProps {| PropA = "hello"; PropB = "world" |}
// The type '{| PropA: 'a |}' does not support the operator 'get_PropB'
doThingsWithOrProps {| PropA = "wer" |}
// The type '{| PropB: 'a |}' does not support the operator 'get_PropA'
doThingsWithOrProps {| PropB = "wer" |}
===
使用https://sharplab.io将上面 F# 代码的有效部分转换为 C# 的其他位(略读一些关于相等性、哈希码的部分,以使其更简洁)
let inline (|PropA|) source =
(^Source: (member PropA: 'PropA) source)
let inline (|PropB|) source =
(^Source: (member PropB: 'PropB) source)
let inline (|PropAAndB|) (PropA (propA: 'PropA) & PropB (propB: 'PropB)) = (propA, propB)
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithAndProps (PropAAndB (propA: 'PropA, propB: 'PropB)) =
printfn $"({nameof propA} = %A{propA}, {nameof propB} = %A{propB})"
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
// Compiles just fine
doThingsWithAndProps {| PropA = "hello"; PropB = "world" |}
[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyVersion("0.0.0.0")]
[CompilationMapping(SourceConstructFlags.Module)]
public static class @_
{
[SpecialName]
public static Tuple<PropA, PropB> |PropAAndB|$W<a, PropB, PropA>(FSharpFunc<a, PropA> get_PropA, FSharpFunc<a, PropB> get_PropB, a _arg1)
{
PropA item = get_PropA.Invoke(_arg1);
PropB item2 = get_PropB.Invoke(_arg1);
return new Tuple<PropA, PropB>(item, item2);
}
[SpecialName]
public static b |PropAOrB|<a, b>(a _arg1)
{
if (false)
{
return (b)(object)null;
}
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
[SpecialName]
public static b |PropAOrB|$W<a, b>(FSharpFunc<a, b> get_PropA, FSharpFunc<a, b> get_PropB, a _arg1)
{
return get_PropA.Invoke(_arg1);
}
public static void doThingsWithAndProps<a, PropB, PropA>(a _arg1)
{
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
PropA val = (PropA)(object)null;
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropB is not supported");
}
PropB val2 = (PropB)(object)null;
object[] array = new object[4];
array[0] = "propA";
array[1] = val;
array[2] = "propB";
array[3] = val2;
Type[] array2 = new Type[2];
array2[0] = typeof(PropA);
array2[1] = typeof(PropB);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, PropA, string, PropB>>("(%P() = %A%P(), %P() = %A%P())", array, array2));
}
public static void doThingsWithAndProps$W<a, PropB, PropA>(FSharpFunc<a, PropA> get_PropA, FSharpFunc<a, PropB> get_PropB, a _arg1)
{
PropA val = get_PropA.Invoke(_arg1);
PropB val2 = get_PropB.Invoke(_arg1);
object[] array = new object[4];
array[0] = "propA";
array[1] = val;
array[2] = "propB";
array[3] = val2;
Type[] array2 = new Type[2];
array2[0] = typeof(PropA);
array2[1] = typeof(PropB);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, PropA, string, PropB>>("(%P() = %A%P(), %P() = %A%P())", array, array2));
}
public static void doThingsWithOrProps<a, b>(a _arg1)
{
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
b val = (b)(object)null;
object[] array = new object[2];
array[0] = "propAOrB";
array[1] = val;
Type[] array2 = new Type[1];
array2[0] = typeof(b);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, b>>("%P() = %A%P()", array, array2));
}
public static void doThingsWithOrProps$W<a, b>(FSharpFunc<a, b> get_PropA, FSharpFunc<a, b> get_PropB, a _arg1)
{
b val = get_PropA.Invoke(_arg1);
object[] array = new object[2];
array[0] = "propAOrB";
array[1] = val;
Type[] array2 = new Type[1];
array2[0] = typeof(b);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, b>>("%P() = %A%P()", array, array2));
}
}
namespace <StartupCode$_>
{
internal static class $_
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly <>f__AnonymousType1562431155<string, string> _arg1@11;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly string activePatternResult1923786_0@18;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly string activePatternResult1923786_1@18;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly PrintfFormat<Unit, TextWriter, Unit, Unit> format@1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
[DebuggerNonUserCode]
internal static int init@;
static $_()
{
_arg1@11 = new <>f__AnonymousType1562431155<string, string>("hello", "world");
activePatternResult1923786_0@18 = @_._arg1@11.PropA;
activePatternResult1923786_1@18 = @_._arg1@11.PropB;
object[] array = new object[4];
array[0] = "propA";
array[1] = @_.activePatternResult1923786_0@18;
array[2] = "propB";
array[3] = @_.activePatternResult1923786_1@18;
Type[] array2 = new Type[2];
array2[0] = typeof(string);
array2[1] = typeof(string);
format@1 = new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, string, string, string>>("(%P() = %A%P(), %P() = %A%P())", array, array2);
PrintfModule.PrintFormatLineToTextWriter(Console.Out, @_.format@1);
}
}
}
您将运行时与编译时混淆了。
SRTP 约束在编译时起作用。 编译器必须确保在运行时出现时可能传递给您的函数的任何值肯定会满足 SRTP 约束。
另一方面,主动匹配器在运行时工作。 事先不知道传递给活动匹配器的值是什么。 主动匹配器获取值、查看它并确定应如何对其进行分类。 这就是主动匹配器的全部要点:它通过一些事先不知道的分类对值进行分类。
因此,当您制作像PropAOrB
这样的活动匹配器时,编译器会发现,当在运行时将值传递给它时,您的匹配器必须首先调用PropA
以查看它是否与该值匹配,如果不匹配 - 调用PropB
看看它是否匹配。 这意味着,可能会调用PropA
和PropB
,因此,可以传递给PropAOrB
的值的静态(即提前已知)类型必须同时满足PropA
和PropB
的 STRP 约束。
如果您想让PropAOrB
接受仅具有PropA
的值或仅具有PropB
的值或同时具有两者的值,一种选择是在运行时使用反射进行匹配:
let inline (|PropA|_|) source =
let prop = source.GetType().GetProperty("PropA")
if prop = null then None else Some (PropA (prop.GetValue(source)))
let inline (|PropB|_|) source =
let prop = source.GetType().GetProperty("PropB")
if prop = null then None else Some (PropB (prop.GetValue(source)))
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
doThingsWithOrProps {| PropA = "wer" |}
doThingsWithOrProps {| PropB = "wes" |}
但是当然,现在PropA
和PropB
都返回object
(因为那是PropertyInfo.GetValue
返回的对象),所以我猜你必须知道类型并转换它。
另外,您必须处理不完整的模式匹配,因为还有第三种未处理的可能性:该值既没有PropA
也没有PropB
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.