I'm wondering why the |, "or" pattern in a SRTP context in my function doThingsWithOrProps isn't working as expected below, aka it should be able accept a type that has either a property PropA or another property PropB, but it is interpreted as an AND & pattern instead like for the function doThingsWithAndProps which doesn't really make sense since the pattern expected for the input of the respective two functions is clearly different.
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" |}
===
Additional bits using https://sharplab.io to convert valid parts of the F# code above to C# (skimming some part parts about equality, hashcode, to making a little terser)
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);
}
}
}
You're confusing runtime with compile time.
The SRTP constraints work at compile time. The compiler has to make sure that any values that could possibly be passed to your function when runtime comes around will definitely satisfy the SRTP constraint.
Active matchers, on the other hand, work at runtime. It's not known ahead of time what the value passed to the active matcher will be. The active matcher gets the value, looks at it, and determines how it should be classified. That's the whole point of an active matcher: it classifies a value by some classification that's not known in advance.
So when you make an active matcher like
PropAOrB, the compiler sees that, when a value is passed to it at runtime, your matcher will have to first callPropAto see if it matches the value, and if it doesn't - callPropBand see if it matches. Which means that, potentially, bothPropAandPropBwill be called, and therefore, the static (i.e. known in advance) type of the values that can be passed toPropAOrBmust satisfy bothPropA's andPropB's STRP constraints.If you wanted to let
PropAOrBaccept either values that have onlyPropAor values that have onlyPropBor values that have both, one option would be to match at runtime using reflection:But of course, now both
PropAandPropBreturnobject(because that's whatPropertyInfo.GetValuereturns), so you'd have to know the type and cast it I guess.Plus, you'd have to deal with incomplete pattern matches, because there is a third unhandled possibility: the value has neither
PropAnorPropB.