C# types for empty F# discriminated union cases

删除回忆录丶 提交于 2019-12-13 14:27:11

问题


I'm accessing an F# discriminated union using C# and trying to use a switch statement over the union's cases. This works fine for values that have at least one field but not for empty values as these don't have a corresponding class generated, only a property. Consider the following F# discriminated union.

type Letter = A of value:int | B of value:string | C | D

In C# I have the following switch statement inside a function that has an argument letter of type Letter:

switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    default:
        if (letter.IsC) Console.WriteLine("C");
        else if (letter.IsD) Console.WriteLine("D");
}

The default case handles the cases where the union value is empty. I'd much prefer:

switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    case C c: Console.WriteLine("C"); break;
    case D d: Console.WriteLine("D"); break;
}

But this doesn't work because the type names C and D do not exist - C and D are properties not types. I can circumvent this by giving C and D a field of type unit but it's not very elegant. Why are types only created for non-empty discriminated union values and what's the best workaround?


回答1:


I do not think that guessing why F# DUs were implemented the way the Language spec part 8.5.4 Compiled Form of Union Types for Use from Other CLI Languages prescribed would be utterly important when using F# DUs from C#.

A good design for such interop scenario would be avoid using "raw" DU, hiding instead this implementation detail behind some interface that F# would expose to other CLI languages.

On few occasions (e.g. this one and that one) the matter with use of F# DU from C# was covered on SO and recommendations were given on how to do it the right way.

But if you would insist on a wrong way with your C# relying upon the specifics of F# DU implementation, the following C# hack will do:

namespace ConsoleApp1
{
    class Program {

        private static void unwindDU(Letter l)
        {
            switch (l.Tag)
            {
                case Letter.Tags.A: Console.WriteLine(((Letter.A)l).value); break;
                case Letter.Tags.B: Console.WriteLine(((Letter.B)l).value); break;
                case Letter.Tags.C: Console.WriteLine("C"); break;
                case Letter.Tags.D: Console.WriteLine("D"); break;
            }
        }

        static void Main(string[] args)
        {
            unwindDU(Letter.NewA(1));
            unwindDU(Letter.C);
        }
    }
}

Being executed it will return

1
C



回答2:


switch (letter)
{
    case A a: Console.WriteLine(a.value); break;
    case B b: Console.WriteLine(b.value); break;
    case Letter l when l == C: Console.WriteLine("C"); break;
    case Letter l when l == D: Console.WriteLine("D"); break;
}

Empty discriminated unions use the singleton pattern with the tag passed through the constructor so the property C is assigned to new Letter(0) and D to new Letter(1) where Letter is the corresponding C# class. The first part of the case statement will always evaluate to true as letter is of type Letter. The when clauses specify that the letter must be equal to the singleton instance of Letter that corresponds to the empty discriminated union values of C and D.




回答3:


If you don't mind adding a little bit of complexity to your type you can define your F# type like so:

type Letter = A of value:int | B of value:string | C of unit | D of unit

Having done that, you can pattern match in C# as follows:

switch (letter)
{
    case A a: Console.WriteLine("A"); break;
    case B b: Console.WriteLine("B"); break;
    case C _: Console.WriteLine("C"); break;
    case D _: Console.WriteLine("D"); break;
}


来源:https://stackoverflow.com/questions/52123944/c-sharp-types-for-empty-f-discriminated-union-cases

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!