← Back to context

Comment by moomin

1 day ago

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

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>);

Hi there. One of the C# lang designers here.

You're correct. The unions we're working on right now are 'type unions'. So the type is inherent in the union distinction, and you would not be able to distinguish that case. That said, we're also looking at full blown discriminated unions (you can look at one of my proposals for that here: https://github.com/dotnet/csharplang/blob/main/meetings/work...), which would allow for that. Syntax entirely tbd, but you'd do something like:

  enum struct Either<T1, T2> // or enum class
  {
     First(T1 value),
     Second(T2 value)
  }

We view these features as complimentary. Indeed, if you look at the extended enum proposal, you'll see it builds on top of unions and closed types (another proposal coming in the next version of the lang).

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...

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

    • 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.

      12 replies →

You can use implicit operators or a library like Vogen to accomplish the same thing in a way that they can be coerced as strings. This isn’t a real issue.

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

  • No, it's a union of a left value (that happens to be a string) and a right value (that happens to be a string). But the compiler-generated code can't tell them apart.

    • What you are describing is something different called a disjoint union which will maintain the identities of the left and right values when there is overlap.

      The C# unions appear to behave like unions, not disjoint unions.

      1 reply →

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

  • 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?

    • 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.

      1 reply →