具有 F# SRTP 约束的 OR 模式被解释为 AND

OR pattern with F# SRTP constraints is interpreted as AND

提问人:Natalie Perret 提问时间:12/22/2022 最后编辑:Natalie Perret 更新时间:12/23/2022 访问量:111

问:

我想知道为什么我的函数中 SRTP 上下文中的“or”模式没有按预期工作,也就是它应该能够接受具有属性或其他属性的类型,但它被解释为 AND 模式,而不是像函数一样,这并没有真正意义,因为各自两个函数的输入所期望的模式明显不同。|doThingsWithOrPropsPropAPropB&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 = @[email protected];
            activePatternResult1923786_1@18 = @[email protected];
            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);
        }
    }
}
F# 鸭子类型化 SRTP

评论


答:

3赞 Fyodor Soikin 12/22/2022 #1

您将运行时与编译时混淆了。

SRTP 约束在编译时起作用。编译器必须确保在运行时可能传递给函数的任何值都绝对满足 SRTP 约束。

另一方面,活动匹配器在运行时工作。事先不知道传递给活动匹配器的值是什么。活动匹配器获取值,查看该值,并确定应如何对其进行分类。这就是主动匹配器的全部意义所在:它通过事先不知道的分类对值进行分类。

因此,当您创建像 这样的活动匹配器时,编译器会发现,当在运行时将值传递给它时,您的匹配器必须首先调用以查看它是否与该值匹配,如果没有 - 调用并查看它是否匹配。这意味着,可能同时调用 和 ,因此,可以传递到的值的静态(即预先已知)类型必须同时满足 's 和 的 STRP 约束。PropAOrBPropAPropBPropAPropBPropAOrBPropAPropB


如果要允许接受仅具有的值或仅具有的值或同时具有两者的值,则一个选项是在运行时使用反射进行匹配:PropAOrBPropAPropB

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" |}

但是,当然,现在两者都是 return (因为这就是 return),所以你必须知道类型并投射它,我猜。PropAPropBobjectPropertyInfo.GetValue

另外,您必须处理不完整的模式匹配,因为还有第三种未处理的可能性:值既没有也没有。PropAPropB

评论

0赞 Natalie Perret 12/27/2022
感谢您的回答,非常感谢,我只是认为有一些实际的编译时支持,可惜事实并非😕如此。再次感谢。
0赞 Fyodor Soikin 12/27/2022
编译器时支持到底是什么?我的意思是,你期望它如何工作?
0赞 Natalie Perret 12/30/2022
哦,好吧,如果不清楚,对不起,只是认为有一个编译器技巧可以通过活动匹配器接受和约束具有 AND 属性/OR 属性的参数类型。我只是假设并且没有花时间检查它如何翻译,IL-wise / C# eq with sharplab.io