Remix.run Logo
moomin 5 hours ago

AFAICT, this means you won’t be able to define Either<string, string>, which is definitely a thing you sometimes want to do.

sheept 5 hours ago | parent | next [-]

It seems like if you wrap both in a record then it should be possible:

    public record Left<T>(T Value);
    public record Right<T>(T Value);
    public union Either<L, R>(Left<L>, Right<R>);
jzebedee 5 hours ago | parent | prev | next [-]

C# is strongly-typed, not stringly-typed. The point of the union is to list possible outcomes as defined through their respective types.

The idiomatic way to do this would be to parse, don't validate [1] each string into a relevant type with a record or record struct. If you just wanted to return two results of the same type, you'd wrap them in a named tuple or a record that represented the actual meaning.

[1] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...

nesarkvechnep 5 hours ago | parent [-]

I guess C# is more strongly-typed than Haskell then... /s

troad 2 hours ago | parent | next [-]

String literal typing appears to be a common feature of type systems bolted onto dynamic languages:

    # Python
    MyStringBool = Literal("Yes") | Literal("No")

    // TypeScript
    type MyStringBool = "Yes" | "No"
I assume it exists to compensate for the previous lack of typing, and consequent likelihood of ersatz typing via strings.

It would seem pretty unnecessary in Haskell, where you can just define whatever types you want without involving strings at all:

    data MyBool = Yes | No
Of course you'd need a trivial parser, though this is probably a good idea for any string type:

    parseMyBool :: String -> MyBool 
    parseMyBool "Yes" = Yes
    parseMyBool "No" = No
    parseMyBool _ = error "..."
Interestingly, dynamic languages which make use of symbols (Ruby, Elixir, Common Lisp) probably fall closer to Haskell than Python or TS. Elixir example:

    @type my_bool() :: :yes | :no

    @spec parse_my_bool(String.t()) :: my_bool()
    def parse_my_bool("Yes"), do: :yes
    def parse_my_bool("No"), do: :no
    def parse_my_bool(_), do: throw("...")
Where :yes and :no are memory-efficient symbols, not strings.
goto11 3 hours ago | parent | prev [-]

You cant have a `type Foo = String | Strimg` in Haskell either.

goto11 3 hours ago | parent | prev | next [-]

Well it is a type union. The union of string and string is just string.

throw1234567891 5 hours ago | parent | prev [-]

but can you define T1 and T2 of string, then use Either<T1, T2>?

3 hours ago | parent | next [-]
[deleted]
eterm 4 hours ago | parent | prev [-]

Could you be clearer about what you mean, since string is a sealed type in C#, so what exactly do you mean T1 and T2 of string?

rawling 4 hours ago | parent [-]

A record wrapping a string, indicating what the string represents, so you can't mix it up with a different thing also represented by a string.

eterm 4 hours ago | parent [-]

Yes, you can have two different record types which both wrap a string value.

As a (bad) trivial example, you could wrap reading a file in this kind of monstrosity:

    var fileResult = Helpers.ReadFile(@"c:\temp\test.txt");

    Console.WriteLine("Extracted:");
    Console.WriteLine(Helpers.ExtractString(fileResult));

    public record FileRead(string value);
    public record FileError(string value);
    public union FileResult(FileError, FileRead);

    public static class Helpers
    {
        public static FileResult ReadFile(string fileName)
        {
            try
            {
                var fileResult = System.IO.File.ReadAllText(fileName);
                return new FileRead(fileResult);
            }
            catch (Exception ex)
            {
                return new FileError(ex.Message);
            }
        }

        public static string ExtractString(FileResult result)
        {
            return result switch
            {
                FileError err => $"An Error occured: {err.value}",
                FileRead content => content.value,
                _ => throw new NotImplementedException()
            };
        }
    }

Now, such an example would be an odd way to do things ( particuarly because we're not actually avoiding the try/catch inside ), but you get the point. Both FileRead(string value) and FileError(string value) wrap strings in the same way, but are different record types, and the union FileResult ties them back together in a way where you can tell which you have.

It's more useful implemented a level deeper, so that the exception is never raised and caught, because exceptions aren't particularly cheap in .NET.