1 पॉइंट द्वारा GN⁺ 2024-08-09 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • C# में type unions (discriminated unions) लाने का प्रस्ताव है, जिससे एक variable या parameter केवल सीमित कई types में से किसी एक को ही रख सके; यह फिलहाल Proposed चरण में है, और prototype·implementation·specification की स्थिति Not Started है
  • मौजूदा inheritance hierarchy या object-आधारित implementation के लिए closed type set, असंबंधित types का संयोजन, wrapper के बिना value storage, और allocation से बचाव—इन सभी को एक साथ संतुष्ट करना कठिन है, इसलिए इसे चार union श्रेणियों में बाँटा गया है
  • प्रस्ताव union class, union struct, ad hoc union, और custom union में फर्क करता है, और हर तरीके पर declaration style, allocation, और existing types के पुन: उपयोग की क्षमता के हिसाब से अलग constraints हैं
  • switch और pattern matching में सभी member types को handle करने पर exhaustiveness मिलती है, इसलिए default की ज़रूरत नहीं होती; लेकिन union struct में default किसी declared member से मेल नहीं भी खा सकता, इसलिए warning और explicit default member declaration की ज़रूरत होती है
  • ad hoc union (A or B or C) syntax से existing types को जोड़ता है और erasure व runtime checks से implement होता है; लेकिन इसमें value type boxing, ref type असमर्थन, और true runtime overloading न होने जैसी सीमाएँ हैं

प्रस्ताव का उद्देश्य और प्रेरणा

  • Type unions for C# C# में type unions, यानी discriminated unions, जोड़ने का प्रस्ताव है
    • दस्तावेज़ की स्थिति Proposed है
    • Prototype, Implementation, और Specification सभी को Not Started के रूप में दिखाया गया है
  • software development में अक्सर ऐसी स्थिति आती है जहाँ एक variable में हर बार एक ही तरह का नहीं, बल्कि सीमित संबंधित types में से किसी एक को रखना होता है
    • उदाहरण के लिए Customer और Supplier जैसे types, जिनमें कुछ properties साझा होती हैं, लेकिन उनके फर्क के आधार पर मिलते-जुलते operations करने होते हैं
  • common abstract methods या interfaces के जरिए implementation को हर type में बाँटना तब उपयुक्त होता है जब वह operation उसी type के अस्तित्व का कारण हो या उसके स्वभाव का मूल हिस्सा हो
    • अगर type का उद्देश्य उससे व्यापक है, तो ऐसे methods जोड़ना उचित नहीं भी हो सकता
  • inheritance से Contact जैसा common base type बनाया जा सकता है, लेकिन निम्न स्थितियों में यह कठिन या अनुपयुक्त होता है
    • जब आप type definitions के owner न हों
    • जब ऐसी कई समान स्थितियाँ हों और inheritance से केवल एक ही हल किया जा सके
    • जब किसी खास operation की requirements को data definition में उजागर नहीं करना चाहते हों
  • object का उपयोग करके यह काम किया जा सकता है, लेकिन सही values ही आएँगी, इसकी गारंटी documentation और comments से संभालनी पड़ती है
    • wrapper hierarchy या custom aggregate type से सुरक्षा की जा सकती है, लेकिन अगर context के अनुसार type sets बहुत हों, तो यह समय लेने वाला और झंझटपूर्ण हो जाता है
  • लक्ष्य यह है कि C# में सीमित कई types में से किसी एक को उसी स्थान पर store करने की declaration हो सके, और variable की सुरक्षा भाषा खुद संभाले

चार union श्रेणियाँ

  • सभी use cases को एक ही implementation से पूरा करना कठिन है, इसलिए इन्हें चार श्रेणियों में बाँटा गया है
  • Standard - union classes

    • जब union और उसके members को साथ में define किया जा सके, और members को स्वतंत्र class की तरह इस्तेमाल करने का इरादा हो, तब इसका उपयोग होता है
    • यह उन मामलों के लिए है जहाँ class allocation समस्या नहीं है
    • उदाहरण:
      • protocol, serialization, data transfer types
      • UI data model (XAML)
      • syntax tree
      • कम बार बदलने वाली state machine states
      • अन्य polymorphic data models
      • ऐसे values जो field या property की तरह लंबे समय तक union रूप में बने रहते हैं
  • Specialized - union structs

    • जब allocation से बचना ज़रूरी हो या special types की ज़रूरत हो, और उसके लिए कुछ restrictions स्वीकार की जा सकें, तब इसका उपयोग होता है
    • उदाहरण:
      • continuous array में allocate होने वाले values
      • memory block पर map होने वाले values (interop)
      • बार-बार बदलने वाली state machine states
      • arguments या return values जैसे values जो union रूप में कम समय तक रहते हैं
      • library types जिनमें special use की संभावना हो
  • Ad Hoc - ad hoc unions

    • जब पहले से मौजूद और आपस में असंबंधित हो सकने वाले types से union बनाना हो, तब इसका उपयोग होता है
    • एक जैसे member types से घोषित unions आपस में interchangeable होने चाहिए
  • Custom unions

    • यह उन मामलों के लिए है जो दूसरी श्रेणियों में ठीक से फिट नहीं बैठते
    • उदाहरण:
      • existing types और hierarchies जिन्हें आसानी से redefine नहीं किया जा सकता
      • custom storage layout
      • custom API shape और behavior

Standard - union classes

  • union class एक named type union है जिसमें सभी member types एक self-contained declaration के अंदर रखे जाते हैं
  • इसकी declaration enum जैसी दिखती है, लेकिन फर्क यह है कि हर member ऐसा type होता है जिसमें एक या अधिक state variables के रूप में state हो सकती है
union U
{
    A(int x, string y);
    B(int z);
    C;
}
  • हर member के लिए केवल name और state variables की सूची दी जा सकती है
  • निर्माण member type को assign करके किया जाता है
U u = new A(10, "ten");
  • बने हुए member का type A होता है, और variable u में assign होते समय यह U में convert हो जाता है
  • deconstruction type test और pattern matching से की जाती है
if (u is A a) { ... }

if (u is A(var x, var y)) { ... }

if (u is A { y: var y }) { ... }
  • union class को exhaustive माना जाता है
    • switch expression या statement में सभी member types को handle कर लिया जाए, तो default case की ज़रूरत नहीं होती
var x = u switch {
    A a => a.x,
    B b => b.z,
    C c => 0
    };
  • null को standard nullable notation से शामिल किया जा सकता है
U? u = null;
  • implementation को abstract record class और nested derived record class से व्यक्त किया जाता है
[Closed]
abstract record U
{
    public record A(int x, string y) : U;
    public record B(int z) : U;
    public record C : U { public static C Singleton = new C(); };
}
  • Closed attribute भाषा को यह समझने देता है कि base type module के बाहर subtypes घोषित नहीं किए जाएँगे, यानी यह एक closed hierarchy है

Specialized - union structs

  • union struct भी एक named type union है जिसमें सभी member types एक self-contained declaration के अंदर रखे जाते हैं
    • union और member types दोनों struct होते हैं, इसलिए इन्हें heap allocation के बिना इस्तेमाल किया जा सकता है
  • इसकी declaration union class जैसी है, लेकिन इसमें struct keyword जोड़ा जाता है
union struct U
{
    A(int x, string y);
    B(int z);
    C;
}
  • निर्माण, deconstruction, exhaustive switch, और nullable notation union class जैसे ही हैं
U u = new A(10, "ten");

if (u is A a) { ... }

U? u = null;
  • union struct uninitialized रह सकता है या default assign होने पर undefined state में जा सकता है
    • यह state किसी भी declared member type से मेल नहीं खाती
    • exhaustiveness पर निर्भर switch में runtime exception हो सकता है
U u = default;

var x = u switch
{
    A a => a.x,
    B b => b.z,
    C c => 0
}
  • compiler, struct union में default assign होने पर warning देता है
// warning: default not a valid state
U u = default;
  • warning से बचने के लिए union struct में default state घोषित करके उसे किसी खास member type से जोड़ा जा सकता है
union struct U
{
    A(int x, string y);
    B(int z);
    C = default;
}
  • implementation को nested record struct member types और member types तथा aggregate union struct के बीच convert करने वाली API वाले struct के रूप में व्यक्त किया गया है
    • internal layout को compiler इस तरह चुनता है कि संभावित member types के data को कुशलता से store किया जा सके
    • speed और size के बीच का tradeoff भी compiler ही चुनता है
[Union]
struct U
{
    public record struct A(int x, string y);
    public record struct B(int z);
    public record struct C { public static C Singleton = default; };

    public static implicit operator U(A value) {...};
    public static implicit operator U(B value) {...};
    public static implicit operator U(C value) {...};

    public static explicit operator A(U union) {...};
    public static explicit operator B(U union) {...};
    public static explicit operator C(U union) {...};

    public bool TryGetA(out A value) {...};
    public bool TryGetB(out B value) {...};
    public bool TryGetC(out C value) {...};

    public enum UnionKind { A = 1, B = 2, C = 3 };
    public UnionKind Kind => {...};
}
  • Union attribute यह पहचानता है कि संबंधित type एक union struct है
  • default state वाले union struct में संबंधित UnionKind को 0 के रूप में घोषित किया जाता है
  • union struct की पूरी construction API अभी दस्तावेज़ में नहीं दिखाई गई है

union struct का type test, boxing, reflection

  • ज्ञात union struct पर type test करने पर, struct के अपने type की जाँच नहीं की जाती बल्कि union struct API को call किया जाता है
u is A a
  • ऊपर का expression इस तरह convert होता है
u.TryGetA(out var a)
  • switch expression भी Kind और TryGetX call का उपयोग करने वाले रूप में convert होता है
u.Kind switch {
   U.UnionKind.A when u.TryGetA(out var a) => a.x,
   U.UnionKind.B when u.TryGetB(out var b) => b.z,
   U.UnionKind.C when u.TryGetC(out var c) => 0,
   _ => throw ...;
}
  • boxed union struct में member type value boxed नहीं होता, बल्कि union struct खुद boxed होता है
    • union struct का मुख्य use case boxing से बचना है, लेकिन कभी-कभी boxing की ज़रूरत पड़ सकती है
  • चूँकि union struct type और member एक-दूसरे से संबंधित माने जाते हैं, boxed union struct को member type के रूप में type test करके unbox किया जा सकता है
U u = ...;
object value = u;

if (value is A a) {...}
  • ऊपर का code इस तरह convert होता है
if (value is A a || (value is U u && u.TryGetA(out a))) {...}
  • इसके उलट boxed member type को भी union struct के रूप में test और unbox किया जा सकता है
A a = ...;
object value = a;

if (value is U u) {...}
  • अगर type test के दोनों पक्षों का union struct से संबंध statically पता न हो, तो type test fail हो जाता है
bool IsType(object value) => value is T;

U u = new A(...);

if (IsType(u)) {...}
  • reflection का उपयोग करते समय boxed member type A को boxed union struct U में convert करना पड़ सकता है
  • struct union feature runtime में boxed union struct और boxed member type के बीच convert करने के लिए utility methods देता है
public static class TypeUnion
{
    public bool TryConvert(Type unionType, object value, out object? boxedUnion);
    public bool TryConvert(object value, out TUnion union);
    public object? GetValue(object? boxedUnion);
}
  • union class और ad hoc union reflection में पहले से ही सही रूप में होते हैं, इसलिए conversion की ज़रूरत नहीं होती

Ref union structs

  • ref modifier वाला union struct state variable के रूप में refs या ref structs को शामिल कर सकता है
ref union struct U
{
    A(ref int x);
    B(ReadOnlySpan y);
    C;
}
  • इस स्थिति में union implementation और ref struct value वाले member types, ref struct में convert हो जाते हैं
ref struct U
{
    public ref struct A { public ref int x; public A(ref int x) {...}; }
    public ref struct B { public ReadOnlySpan y; public B(ReadOnlySpan y) {...} }
    public record struct C { public static C Singleton = default; }
    ...
}
  • अगर C# में ref record struct type जोड़ा जाता है, तो प्रभावित member types record struct के रूप में बने रह सकते हैं

Ad Hoc - ad hoc unions

  • ad hoc union एक anonymous union है, जो कहीं और declare किए गए types से बनाया जाता है
  • इसका syntax parentheses और or pattern syntax का उपयोग करता है
(A or B or C)
  • common नाम से reference करने के लिए file या global using alias का उपयोग किया जाता है
global using U = (A or B or C);
  • construction union member types में से किसी एक instance को ad hoc union type variable में assign करके की जाती है
record A(int x, string y);
record B(int z);
record C() { public static C Singleton = new C(); };

(A or B or C) u = new A(10, "ten");
  • deconstruction type test और pattern matching से की जाती है
if (u is A a) {...}

if (u is A(var x, var y)) { ... }
  • ad hoc union को भी exhaustive माना जाता है, इसलिए अगर सभी member types को handle कर लिया जाए तो default case की ज़रूरत नहीं होती
  • null को nullable notation के साथ शामिल किया जा सकता है
(A or B)? x = null;
  • एक ही member types वाले ad hoc unions को compiler क्रम से स्वतंत्र होकर same type के रूप में समझता है
(A or B) x = new A(10, "ten");
(B or A) y = x;

ad hoc union का assignment, interchangeability, inference

  • same type या subset वाले ad hoc union को superset ad hoc union में runtime check के बिना assign किया जा सकता है
(A or B) x = new A(10, "ten");
(A or B or C) y = x;
  • superset ad hoc union को subset ad hoc union में assign करने के लिए explicit coercion और runtime check की ज़रूरत होती है
(A or B or C) x = new A(10, "ten");
var y = (A or B)x;
  • अगर source union के सभी member types, target union के कम-से-कम एक member के समान या उसके subtype हों, तो runtime check के बिना implicit coercion संभव है
(Chihuahua or Siamese) pet = ...;
(Cat or Dog) animal = pet;
  • भले ही ऐसा न हो, अगर source member types में से एक या अधिक target member types में से किसी एक के subtype हों, तो explicit coercion और runtime check संभव हैं
(Cat or Chihuahua) mostlyCats = ...;
(Dog or Siamese) mostlyDogs = (Dog or Siamese)mostlyCats;
  • ad hoc union न होने वाले types को भी assignability के आकलन में single-type ad hoc union की तरह देखा जा सकता है
    • यह नियम implemented interfaces पर भी लागू होता है
  • generalized coercions भी परिभाषित किए गए हैं
    • अगर किसी type से union के member types में से किसी एक तक implicit coercion है, तो उस type के value को union type में implicit coercion किया जा सकता है
    • अगर union के सभी member types किसी type तक implicit coercion कर सकते हैं, तो union value को उस type में implicit coercion किया जा सकता है
    • अगर union के member types में से कोई एक किसी type तक coercion कर सकता है, तो union value को उस type में explicit coercion किया जा सकता है
    • अगर source union के सभी member types, target union के member types में से किसी एक तक implicit coercion कर सकते हैं, तो unions के बीच implicit coercion संभव है
    • अगर source union के member types में से एक या अधिक, target union के member types में से किसी एक तक explicit coercion कर सकते हैं, तो unions के बीच explicit coercion संभव है
  • जब कई coercions संभव हों, तब कौन-सा coercion चुना जाए, इसके नियम अभी भी आवश्यक हैं
  • यह assignability संबंध subtyping संबंध नहीं है
    • एक ad hoc union, दूसरे ad hoc union का subtype नहीं है
  • समान member types वाले ad hoc unions, generics और array elements के जरिए परस्पर बदले जा सकते हैं
(T1 or T2)[] F(T1 v1, T2 v2) => new (T1 or T2)[] { v1, v2 };

(Dog or Cat)[] pets = F(rufus, petunia);
  • generic type arguments के रूप में इस्तेमाल किए गए ad hoc unions को covariance और contravariance में इस्तेमाल किया जा सकता है, जब संबंधित दोनों unions के सभी member types अपने-अपने counterpart member के साथ subtype संबंध रखते हों
    • ठोस नियमों की जगह अभी “Have Mads write this part” नाम का एक note छोड़ा गया है
  • ad hoc union, pattern matching में or pattern की तरह काम करता है, और इसके साथ variable declaration भी हो सकती है
if (u is Dog or Cat) { ... }

if (u is (Dog or Cat)) { ... }

if (u is (Dog or Cat) pet) {...}
  • ad hoc union variable को assign करने पर value type boxing हो सकती है
  • conditional expression और switch expression, constituent expressions से ad hoc union result type infer कर सकते हैं
Dog rufus = ...;
Cat petunia = ...;
Bird polly = ...;

var u =
      x == 1 ? rufus
    : x == 2 ? petunia
    : polly;
  • lambda expression का return type भी lambda body के return types से बने ad hoc union के रूप में infer किया जा सकता है
    • इस स्थिति में भी value type boxing हो सकती है

ad hoc union का implementation और सीमाएँ

  • ad hoc union को erasure और runtime checks से implement किया जाता है
(A or B) ab = new A(10, "ten");
  • ऊपर का code इस तरह transform होता है
object ab = new A(10, "ten");
  • जिन assignments की static correctness पता नहीं चल सकती, उनके लिए runtime check की आवश्यकता होती है
    • compiler module में इस्तेमाल किए गए हर unique ad hoc union के लिए custom method generate करता है
object value = ...;
var ab = (A or B)value;
  • transformation का उदाहरण इस प्रकार है
object value = ...;
object ab = (value);

object (object? value) =>
    value is A or B ? value : throw ...;
  • method entry पर parameters की जाँच नहीं की जाती
  • metadata में custom attributes का उपयोग करके ad hoc union types को encode किया जाता है
void M((A or B) x);
  • transformation का उदाहरण इस प्रकार है
void M([AdHocUnion([typeof(A), typeof(B)])] object x);
  • attribute के विवरण अभी निर्दिष्ट नहीं किए गए हैं
  • क्योंकि सभी ad hoc unions एक ही type में erase हो जाते हैं, ad hoc union parameter वाले methods के लिए true runtime overloading संभव नहीं है
public void Wash((Cat or Dog) pet) { ... }
public void Wash((Compact or Sedan) car) { ... }
  • overloading अभी भी चर्चा के लिए खुला क्षेत्र है

Custom unions

  • अगर union class या union struct syntax से व्यक्त न किए जा सकने वाले behavior की ज़रूरत हो, तो आप सीधे custom class या struct declare कर सकते हैं और C# उसे custom union type के रूप में पहचान सकता है
  • अगर class hierarchy से union को implement किया गया है, तो Closed attribute देने पर union class जैसा exhaustiveness behavior मिलता है
[Closed]
public class U { ... }
public class A(int x, string y) : U { ... }
public class B(int z) : U { ... }
  • अगर specialized storage rules वाले struct wrapper से union implement किया गया है, तो Union attribute लगाकर और union pattern का पालन करने वाला API देकर उसे functional रूप से union struct के बराबर बनाया जा सकता है
[Union]
public struct U
{
    public record struct A(int x, string y);
    public record struct B(int z);

    public bool TryGetA(out var A a) { ... }
    public bool TryGetB(out var B b) { ... }
}
  • अगर union में member types शामिल नहीं हैं या वह कोई अलग API pattern इस्तेमाल करता है, तो compiler जिस API की अपेक्षा करता है, उसे extensions के जरिए दिया जा सकता है
  • union struct API pattern का पूरा स्वरूप अभी निर्दिष्ट नहीं है
  • ad hoc union, individual member types के behavior को बदलने के अलावा, अपने behavior को customize नहीं कर सकता

Common unions: Option और Result

  • Option, दूसरी भाषाओं में इसी नाम या इसी उद्देश्य वाले type की तरह एक struct union है
    • यह दर्शाता है कि कोई value मौजूद भी हो सकती है और नहीं भी
public union struct Option
{
    Some(TValue value);
    None = default;
}
  • उपयोग का उदाहरण इस प्रकार है
Option x = new Some("text");
Option y = None;

if (x is Some(var value)) {...}

var v = x is Some(var value) ? value : 0;
  • Option type अभी पूरी तरह निर्दिष्ट नहीं है
  • Result भी दूसरी भाषाओं में इसी नाम या उद्देश्य वाले type की तरह एक struct union है
    • इसका उपयोग function से success result या error return करने के लिए किया जाता है
public union struct Result
{
    Success(TValue value);
    Failure(TError error);
}
  • उपयोग का उदाहरण इस प्रकार है
Result x = Success("hurray!");
Result y = Failure("boo");

switch (x)
{
    case Success(var value): ...;
    case Failure(var error): ...;
}
  • Result type भी अभी पूरी तरह निर्दिष्ट नहीं है

संबंधित प्रस्ताव

  • इस प्रस्ताव में ऐसे प्रस्तावित या भविष्य में प्रस्तावित होने वाले फीचर्स शामिल हैं जिनके अस्तित्व को मानकर चला गया है
  • Closed Hierarchies

    • अगर abstract base type पर Closed attribute लागू किया जाए, तो declaration module के भीतर मौजूद सभी subtype को एक बंद subtype सेट के रूप में घोषित किया जाता है
    • declaration module के बाहर subtype घोषित करने पर compiler error होगा
    • closed hierarchy को compiler exhaustive मानता है, इसलिए अगर switch में सभी subtype को handle कर लिया जाए तो default case की ज़रूरत नहीं होती
  • Singleton values

    • जिन singleton type में static Singleton property होती है, उन्हें non-type context में उस property को implicit रूप से access करके value की तरह इस्तेमाल किया जा सकता है
var x = U.C.Singleton;
  • ऊपर का कोड इस तरह लिखा जा सकता है
var x = U.C;
  • Nested Member Shorthand

    • unbound name को target type के static member या nested type से bind किया जा सकता है
Color color = Color.Red;
  • ऊपर का कोड इस तरह लिखा जा सकता है
Color color = Red;
U u = new U.A(10, "ten");
  • ऊपर का कोड इस तरह लिखा जा सकता है
U u = new A(10, "ten");

Q&A में स्पष्ट किए गए डिज़ाइन निर्णय और सीमाएँ

  • union class सीधे nested record hierarchy को आसानी से घोषित किया जा सके तो अनिवार्य नहीं है, लेकिन इसका फायदा संक्षिप्त syntax और सिर्फ struct modifier बदलकर union struct में आसानी से बदल पाना है
  • union struct में allocation कम होता है और ज़्यादा तरह के type इस्तेमाल किए जा सकते हैं, लेकिन यह हर स्थिति में बेहतर नहीं होता
    • खुद allocation न होने पर भी यह ज़रूरी नहीं कि ज़्यादा तेज़ हो
    • इसका stack footprint बड़ा होता है और assignment, pass, return के समय आमतौर पर copy होता है
    • anonymous ad hoc union के लिए यह आसानी से interchangeable नहीं है, इसलिए वहाँ यह उपयुक्त नहीं है
    • boxed state में या generic type parameter के रूप में statically represent होने पर type test, cast, pattern matching से जुड़ी समस्याएँ होती हैं
  • union struct आंतरिक रूप से tagged union भी है और type union भी
    • अंदरूनी रूप से यह tag की भूमिका निभाने वाली enum property expose कर सकता है, जिससे compiler-generated code तेज़ हो सके
    • language surface पर यह type union की तरह दिखता है ताकि इसे type test, cast, pattern matching जैसे परिचित तरीकों से संभाला जा सके
  • compiler, union struct member type को सीधे union struct variable में assign करने की स्थिति में member type creation को छोड़ने वाला optimization कर सकता है
  • union को सीधे variable में deconstruct करने पर, union struct state variable को member type में copy करने की प्रक्रिया भी छोड़ देने वाला optimization अपेक्षित है
  • union struct, union class की तरह member type का वास्तविक base type नहीं है
    • struct वास्तविक inheritance की अनुमति नहीं देता
    • तार्किक रूप से यह automatic conversion के ज़रिए base type की तरह काम करता है, लेकिन यह संबंध पूरे type system और runtime तक विस्तृत नहीं होता
  • ad hoc union को अभी मौजूदा नाम से सीधे declare नहीं किया जा सकता
    • लंबे union repetition से बचने या वर्णनात्मक नाम चाहिए हो तो global using alias का उपयोग करना होगा
  • ad hoc union value type को boxing करता है
    • अगर boxing से बचना हो तो union struct का उपयोग करना चाहिए
  • ad hoc union में ref types शामिल नहीं हो सकते
    • अगर ref types चाहिए हों तो union struct का उपयोग करना चाहिए
  • ad hoc union के object में erase होने का कारण यह है कि यह आज डेवलपर्स द्वारा अक्सर इस्तेमाल किए जाने वाले object-आधारित समाधान को compile-time type safety और generated validation checks के साथ बेहतर बनाता है
  • ad hoc union की common property या method को हर type case को handle किए बिना सीधे access नहीं किया जा सकता
    • value तक पहुँच सिर्फ किसी individual type में सफल conversion के बाद ही हो सकती है
  • F# के union types इस specification के union class और union struct के अनुरूप हैं, लेकिन यह प्रस्ताव member को tag state और associated state variables के बजाय भाषा के type के रूप में संभालता है
    • ad hoc union, Typescript के type unions जैसा है
  • Option, अगर null और nullable reference types से वही उद्देश्य पूरा हो सकता हो, तो ज़रूरी नहीं हो सकता
    • कुछ डेवलपर्स C# के nullable types की तुलना में अधिक सख्त enforcement के लिए option type को पसंद करते हैं
  • C# अभी Option और Result के लिए F# में उपलब्ध monadic behaviors को भाषा में शामिल नहीं करता
  • Result के कई use case, C# के exception handling से हल किए जा सकते हैं
    • लेकिन जब runtime पर error की उम्मीद हो और वह आम हो, तब exception handling से बचना और caller से error को स्पष्ट रूप से handle करवाना वांछनीय हो सकता है
  • Option और Result जैसे type third-party libraries में पहले से मौजूद हैं, लेकिन कई डेवलपर्स ने library के बीच interoperability के लिए runtime में standardized type शामिल करने का अनुरोध किया है

1 टिप्पणियां

 
GN⁺ 2024-08-09
Hacker News की राय
  • F# में discriminated unions कुछ साल इस्तेमाल कर चुका हूं, इसलिए लगा था कि C# में भी अब तक यह होना ही चाहिए था
    यह ऐसी feature नहीं होगी जो सबको पसंद आए, लेकिन typed languages में किसी न किसी रूप में algebraic data types (ADT) के बिना वाली language पर लौटना सचमुच मुश्किल है। अभी मैं Java इस्तेमाल कर रहा हूं और कुल मिलाकर ठीक है, लेकिन F# में जो काम तीन लाइनों में हो जाता, उसे wrapper classes से घुमाकर करना पड़े तो काफी खटकता है

    • F# की आदत पड़ जाए तो वापस जाना बहुत मुश्किल है। काश Microsoft इसे और ठीक से support और push करे
      pure functions जैसी constraints में बंधे बिना भी functional programming के काफी फायदे मिल जाते हैं—यह एक बहुत सटीक संतुलन है, इसलिए development आसान लगता है। दूसरी ओर C# को बहुत धीरे-धीरे F# जैसा बनाया जा रहा है, और उसे देखना भी अजीब लगता है
    • अच्छी खबर यह है कि आजकल Java में भी sealed interface और record के साथ अपेक्षाकृत कम boilerplate में यह संभव है
      हालांकि यह बात Java के लिहाज से ही है
    • 2020 में F# और .NET छोड़ते समय तक, CLR में केवल inheritance था और F# के enum भी असल में inheritance से implement किए गए थे
      उदाहरण के लिए Some और None, Option class की derived classes थे, और F# assembly को decompiler में देखने पर यह दिख जाता था। अभी भी वही है या नहीं, पता नहीं, लेकिन यह C# proposal A or B syntax वाले anonymous enum सहित, सिर्फ inheritance के ऊपर syntax sugar से बनाना मुश्किल लगता है। इसलिए इसे काम करने के लिए शायद CLR को enum का first-class support देना होगा
    • संदर्भ के लिए, Java में version 16 से tagged union types हैं
      pattern matching भी है
    • Java में कई साल पहले से ADT मौजूद हैं, इसलिए “तीन लाइनों में होने वाले काम के लिए wrapper classes से घुमाना पड़ता है” वाली बात थोड़ी अजीब लगती है
      Records https://en.wikipedia.org/wiki/Java_version_history#Java_16
      Sealed classes https://en.wikipedia.org/wiki/Java_version_history#Java_17
      और लगभग 1 साल पहले से pattern matching भी है https://en.wikipedia.org/wiki/Java_version_history#Java_21
      हालांकि F# में develop करने के आनंद से मैं काफी सहमत हूं
  • इस proposal को लेकर सचमुच उत्साहित हूं। C# की खूबियां बताते समय हमेशा माफी मांगने जैसे अंदाज में जोड़ना पड़ता था कि यह सबसे बड़ा missing feature है
    इसके अलावा C# में कोई बड़ी language feature की कमी याद करना मुश्किल है। अब यह आ भी जाए, तब भी HN पर लोग C# को 10 साल पहले वाली ही language की तरह treat करते दिखेंगे, यह देखने का भी इंतजार है

    • ढंग के type aliases भी होते तो अच्छा होता, लेकिन आजकल C# coding सच में मजेदार है
    • अच्छा हो या बुरा, C# का design goal ऐसा लगता है कि संभव हर feature, वह भी कई variants में, डालना है
      इसलिए यह हर किसी के हिसाब से fit हो सकता है और strong opinions वाली language की तरह लोगों को दूर नहीं धकेलता, लेकिन शुरुआत में सीखना थोड़ा मुश्किल हो सकता है
  • कोई समझा सकता है कि इसे type unions क्यों कहा जा रहा है? यह नाम पहली बार सुन रहा हूं
    यह ALGOL68 की तरह types के बीच union नहीं, बल्कि ML-family languages के tagged union जैसा दिखता है। ऐसा भी लग रहा है कि क्या C# developers मौजूदा terms की जगह अलग नाम बनाकर इस्तेमाल करते हैं—SelectMany, IEnumerable जैसी चीजों की तरह

    • “tagged” ऐसा लगता है जैसे अंदर type को अलग करने वाला tag होना कोई implementation detail हो
      शायद “union” से पहले “type” लगाकर यह साफ करना चाहा है कि यह type से जुड़ी चीज है। documentation में syntax खुद सिर्फ union दिखता है। FAQ में यह लिखा है:
      Q: Why are there no tagged unions?
      A: Union structs are both tagged unions and type unions. Under the hood, a union struct is a tagged union, even exposing an enum property that is the tag to enable faster compiler generated code, but in the language it is presented as a type union to allow you to interact with it in familiar ways, like type tests, casts and pattern matching.
    • क्योंकि यह proposal कई तरह के unions को cover करता है
      इसमें reference types की closed hierarchy शामिल है जिसे compiler build time पर verify करता है, value type वाला tagged union जिसे compiler closed hierarchy की तरह behave कराने के लिए handle करता है, user-defined types जो arbitrary implementation रखते हुए भी उसी compiler mechanism से जुड़ सकते हैं, और existing types के ad-hoc unions भी शामिल हैं
  • red/blue/white/black pill जैसी रंगों वाली रूपक बातें तो मैं मिस कर गया, लेकिन exhaustive pattern matching वाला union एक बार समझ आ जाए तो उन language features में से है जिनके बिना रहना सबसे मुश्किल लगता है
    मुझे कभी ऐसा नहीं लगा कि मैंने expression problem के निहितार्थ पूरी तरह समझ लिए हैं, लेकिन अभी मेरी hypothesis यह है। पारंपरिक polymorphism के जरिए extension point देना तब सही है जब भविष्य का कोई client, जिसे मैं नहीं जानता, code को extend करेगा; और exhaustive pattern matching वाला union उस code के लिए ज़्यादा फिट है जिसका मालिक मैं या मेरी team है। आमतौर पर बात यह नहीं होती कि कोई बाहर से उस code को extend करना चाहता है, बल्कि business domain की समझ बदलने के साथ core data structure को update करना होता है, और फिर imperative code जहाँ domain structure से मेल नहीं खाता, उन जगहों को compiler errors के रूप में जितना हो सके पकड़ना होता है

    • फर्क समझने के लिए मैंने यह लेख लिखा था
      https://deliberate-software.com/christmas-f-number-polymorph...
    • मुझे लगता है expression problem का मूल object-oriented और functional programming में code extensibility पाने के तरीकों के contrast से समझाया जा सकता है
      object-oriented interface/inheritance approach में base class या interface की नई type variant जोड़ना आसान है, लेकिन नई functionality जोड़नी हो तो मौजूदा सभी types में implement करना पड़ता है, इसलिए कठिन है। functional programming के discriminated union approach में नया function बनाकर union पर matching करने से compiler सभी cases handle होने की guarantee देता है, इसलिए नई functionality जोड़ना आसान है; लेकिन नई type variant जोड़ने पर पूरी codebase में exhaustive pattern matching को update करना पड़ता है, इसलिए कठिन है। Kotlin दोनों तरीकों को काफी अच्छी तरह support करता है, इसलिए उदाहरण के लिए अच्छा है
  • क्या terminology थोड़ी off नहीं है? मेरी जानकारी में TypeScript में union types होते हैं
    लेकिन यह तो F# या Haskell में दिखने वाले discriminated union जैसा लगता है। मेरे हिसाब से discriminated union में named case constructor होना ही फर्क है

    • TypeScript में “union types” हैं, और यह proposal लगता है उन्हें ad hoc unions कह रहा है
      “type union” कम-से-कम ऐसा official term नहीं है जो मैंने सुना हो। यह C# side में sum type समझाने के लिए बनाया गया expression लगता है
    • C# से TypeScript के type union पर जाते हुए मुझे हमेशा अजीब लगता था
      शायद इसे अब तक टालने के पीछे कोई वजह रही होगी। target users को समझना पड़ता है। समय के साथ आदत पड़ जाती है, लेकिन A|B|undefined बहुत ज़्यादा दिखे तो पढ़ना थकाऊ हो जाता है। फिर एक input लेकर तीन या उससे ज़्यादा combinations को लगभग जैसे-तैसे return करने जैसी laziness भी आ जाती है, और call stack में ऊपर जाते-जाते confusion बढ़ता जाता है
      मुझे अच्छा लगता है कि C# इसे constrained तरीके से handle करवाता है। हालांकि जो लोग इस feature की उम्मीद कर रहे हैं, उनके पास कोई compelling logic हो तो सुनना चाहूँगा
  • मैं लंबे समय से C# इस्तेमाल कर रहा हूँ, लेकिन इस proposal में लगता है मैं कुछ miss कर रहा हूँ। use case अच्छी तरह defined नहीं दिखता; क्या कोई practical example दे सकते हैं?
    proposal का example तो ऐसा लगता है कि एक empty interface declare करके और कुछ record classes से उसे “implement” कराकर implement किया जा सकता है। ऐसा करने पर क्या खोता है, यह मुझे साफ नहीं है

    • ऐसी hierarchy इस मायने में open hierarchy है कि processing करते समय यह नहीं पता चलता कि आपने सभी cases handle किए हैं या नहीं
      कोई भी, यहाँ तक कि मेरे code के बाहर का code भी, interface का नया implementation बना सकता है। साथ ही choices में कोई common surface बिल्कुल share न हो, तो सभी types का “interface” खाली रह सकता है, जो object-oriented style में थोड़ा unnatural है। अंदर से यह आखिरकार type hierarchy ही है, लेकिन extension closed है और code जब cases इस्तेमाल करता है तो कोई case छूटने पर compile-time error आता है—manual implementation से यही मुख्य फर्क है
    • सबसे practical example AST या data protocol है
      JSON को उदाहरण लें: JsonValue नाम की एक class में data रखने के बजाय, इसे एक union type बना सकते हैं जो string, boolean, number, union type की array, या string key और उसी तरह के union type value वाले map में से एक हो। Rust के Result जैसे result type भी implement किए जा सकते हैं, ताकि API को value या error return करने वाला define किया जा सके। proposal में generics cover किए गए हैं या नहीं, मैंने नहीं देखा। functional programming world में इसे आमतौर पर algebraic data type कहा जाता है, और इस तरह की type modeling की आदत पड़ जाए तो जिन languages में support नहीं होता वहाँ इसकी बहुत कमी महसूस होती है
      [1] https://en.m.wikipedia.org/wiki/Algebraic_data_type
    • यह library F# style के discriminated union implement करती है
      [0] https://github.com/mcintyre321/OneOf
      मैंने इसे outer layer पर DTO, error आदि को एक में wrap करने वाले Result type return कराने के लिए इस्तेमाल किया था। Result पर pattern matching कर सकते हैं, जिससे error handling आसान हो जाती है, और boundary की function signature बदले बिना model को समय के साथ evolve किया जा सकता है
    • पिछले 5 साल से ज़्यादा समय से empty interface के जरिए F# के discriminated union की नकल करता आ रहा हूँ, और यह बहुत अच्छे से काम करता रहा है
      “switch में सभी case handle नहीं किए” वाली समस्या बहुत ही rare हुई—लगभग 50,000 lines of code में एक बार—और आमतौर पर पहले smoke test run के बाद ठीक कर दी। इसलिए यह कोई बड़ी समस्या नहीं थी। मुझे लगता है .NET team ने अभी तक discriminated union इसलिए नहीं जोड़ा क्योंकि interface से इसे effectively imitate किया जा सकता है
    • simple example Result है, जो Ok(T value) या Error(string message) में से एक हो सकता है
      value पाने के लिए आपको दोनों cases पर switch करना होगा, इसलिए error case को उसी जगह handle करने के लिए मजबूर किया जाता है
  • covariance / contravariance section के नीचे “Note: Have Mads write this part.” लिखा है, यह देखकर हँसी आ गई :)

    • यहाँ Mads से मतलब C# के lead designer Mads Torgersen से है
  • “union struct के अंदरूनी layout के लिए compiler, संभावित member types के data को कुशलता से store करने के लिए speed और size के बीच trade-off चुनेगा” वाला हिस्सा है
    पहले FieldOffset के साथ C# union की black magic बहुत ज़्यादा आज़माकर बुरी तरह सबक सीख चुके व्यक्ति के तौर पर, इसमें एक अफ़सोसनाक समस्या है। pointer/ref values और value types को alias करना UB है। यानी u64 और object के struct union में अलग-अलग fields चाहिए होंगे, जिससे 8 bytes बर्बाद होंगे। जब तक ryujit/GC को यह पता हो, इस तरह update नहीं किया जाता, तब तक यही स्थिति है

    • यह UB नहीं बल्कि अवैध है। ECMA 335, II.10.7 के अनुसार fields को इस तरह overlap कराया जा सकता है, लेकिन object reference जिस offset पर होता है, वह किसी built-in value type या किसी दूसरे object reference के हिस्से द्वारा लिए गए offset से overlap नहीं होना चाहिए
      .NET 8.0 में FieldOffset(0) पर UInt64 Foo और Object Bar को साथ रखने वाला code compile तो हो जाता है, लेकिन load होते समय System.TypeLoadException throw करता है। message का आशय है कि offset 0 पर object field, non-object field के साथ गलत तरीके से aligned है या overlap कर रहा है। दिलचस्प बात यह है कि AOT compiler चेतावनी देता है कि यह method हमेशा throw करेगा, लेकिन C# compiler कोई warning नहीं देता
  • C# के बेहतर object-oriented language बनने के बजाय, ऐसा लगता है कि वह लगातार एक और ज़्यादा बदसूरत F# बनने की कोशिश कर रहा है, यह अफ़सोस की बात है
    उदाहरण के लिए multiple dispatch syntax अब भी इतना clunky क्यों है? समझता हूँ कि pseudo OO ने दुनिया पर कब्ज़ा कर लिया, और लोगों ने उसका विरोध किया, इसलिए सच में object-oriented को अच्छे से करने की कोशिश करने के बजाय “curly braces वाली थोड़ी functional language” बनना relevance बनाए रखने का आसान तरीका रहा होगा

    • दूसरी तरफ़ से भी यही शिकायत है। mainstream languages में पहले व्यापक mutable state और side effects थे, और अब उनमें बस lambda जुड़ गया है
      लेकिन object-oriented पक्ष में क्या missing है? C# या किसी दूसरी language के “object-oriented को अच्छे से करने” की स्थिति में पहुँचने के लिए क्या चाहिए होगा?
    • यहाँ “बेहतर object-oriented” से क्या मतलब है?
      जानना चाहूँगा कि आपके मन में कौन-से concepts या features हैं
  • अभी private constructor वाले nested record और NuGet package https://github.com/shuebner/ClosedTypeHierarchyDiagnosticSup... को साथ इस्तेमाल करके यह guarantee कर रहा हूँ कि switch type में _ case की ज़रूरत न पड़े
    असल में यह इस proposal की “Union Classes” का desugared version है, और पहले से काफ़ी अच्छी तरह काम करता है। फिर भी NuGet package की ज़रूरत हट जाए और syntax sugar भी मिल जाए तो अच्छा होगा, इसलिए यह proposal मुझे पसंद है

    • ध्यान रहे कि record types, private constructor होने पर भी closed नहीं होते
      compiler copy operations के लिए protected constructor generate करता है, और उससे inherit किया जा सकता है। इसे रोकने के लिए खुद protected constructor define करना होगा, और अगर valid case न हो तो runtime exception throw कराना होगा