I’ve started to use Kotlin professionally, and keeping an eye on Rust. Both offer a lot of niceties that I wish we could adapt for Vala, but there’s one that keeps popping up my mind everytime: pattern matching.
The simplest pattern matching we have is C unions, and a lot of c-libs use them. Unfortunately, the current handling of unions in Vala is a disgrace, and there’s no alternative for it. But I believe we can import some syntax from both Kotlin and Rust. Here is my proposal of how should unions work in Vala:
//Opening a bracket defines an "anonymous struct" public union OrderStatus { ACCEPTED, CANCELLED {string reason, string cancelled_by}, REJECTED {string reason}, COMPLETED {DateTime completed_at} ON_HOLD {string reason, Datetime until} } match (order.status) { ACCEPTED -> info("Cool!"); CANCELLED -> debug(@"Not okay, it was cancelled because of $(it.reason) by $(it.cancelled_by)"); REJECTED as that -> info (@"Rejected: $that.reason"); default -> error("What is this?? There's no implicit \"it\" here because it's a catch-all!") } public union NestedUnion { SIMPLE, union COMPLEX { SOFT, HARD{string reason} } } //The additional field belongs to the wrapping struct public union ComplexUnion { FIRST, SECOND, THIRD {string reason}; //parent field, children cannot have a field with the same name uint32 timestamp; } //Maybe this is not a good idea public union VeryComplex { FIRST { override void run() { log("Uy!") } }, SECOND { override void run() { log("Ouch!"); } }, THIRD { override void run() { log("Ay!"); } override void do() { debug ("Yay!"); } } //They are all required to implement it! abstract void run(); //Optionally overriden virtual void do() { } //Can't touch this public void execute() { } } //In this case, they reuse existing datatypes public union ExternalUnion { STRING(string), NUMBER(uint64), THINGY(GLib.Object) } public void method () { var order = new Order(OrderStatus.ON_HOLD("reason", DateTime.now())); var other_order = new Order(OrderStatus.CANCELLED(cancelled_by = "desiderantes", reason = "who knows!")); var nested = NestedUnion.COMPLEX.HARD(reason = "no reason at all"); //'match' can return a value, but all branches should return the same type //this 'match' in particular is exhaustive, so no default needed, but if you return a value from 'match', you have to either //cover all cases or have a default branch NestedUnion another_nested = get_from_network(); var reason = match (another_nested) { SIMPLE -> "Just because"; COMPLEX -> match (it) { SOFT -> "Really easy"; //if not renamed, then you'll lose access to the it from outer context, as it'll be shadowed HARD as that -> that.reason; } } //This errors var complex = ComplexUnion(123456789); var complex = ComplexUnion(); var complex = ComplexUnion.FIRST(); //This should work var complex = ComplexUnion.THIRD(123456789, "I can"); var complex = ComplexUnion.THIRD(reason = "Just because", timestamp = 321654987); match (complex) { //properties from the parent are only accessible from the parent reference, no implicit parent var FIRST -> debug(@"$(complex.timestamp)"); SECOND -> debug ("Oops"); THIRD -> debug @("$(complex.timestamp) by $(it.reason)"); } var external = ExternalUnion.STRING("this string is required"); }
The internal structure (C-wise) of my proposed tagged union is not anything new, it has been done a lot before in C land (here is an explanation from the Rust viewpoint)