import variant;
import std.stdio;

// Setting it up for examples below
void showOverload(int n) { writefln("int"); }
void showOverload(char[] c) { writefln("char[]"); }


void main()
{
//  The base data-structure is the Variant struct, which takes a list of types:
    alias Variant!(int, long, char[]) MyVariant; // MyVariant is a type which can be assigned ints, char[]s, or Foo's.
  
//  This data-type has opAssign overloaded, to allow:
    MyVariant v;
    v = 5; // it now holds an int
    v = 5L; // it now holds a long
    v = "World!"; // now holds a char[]
  
//  The point of interest is that it keeps track of the stored type, and pattern matching (similar to a switch statement)
//  can be used to access the data in a type-safe way:
    mixin(Match!(v, 
             Case!("int n", `writefln("Twice your number is %d", 2*n);`),
             Case!("char[] s", `writefln("Hello ", s);`)));

//  The Match!() statement ensures that the value is only used as an appropriate type.
//  However, it needn't be used with the exact type. Instead (just as a proof of concept), if no exact match is found, 
//  it follows D's overloading rules (to a certain extent), and it will match with any type which can be implicitly 
//  converted to the type specified in the pattern, so
    mixin(Match!(v,
             Case!("real r", `writefln("Your value is a long or an int with a value of ", r);`)));

//  Also as a neat thing, we can express multiple cases with one pattern. The supplied code is then inserted once for each
//  different type specified by the pattern (similar to the quasi-static foreach -- that's the main reason I put it in):
    mixin(Match!(v,
             Case!("{int|char[]} n", `showOverload(n);`)));
//  The above code will print "int" if matched with an int, and "char[]" if matched with a char[]

//  If a pattern is unreachable, you will be told at compile time:
    mixin(Match!(v,
             Case!("real r", `writefln("Matches int and long");`)
//            , Case!("int n", `writefln("Matches int");`) // This line would give an error:
                                                           // "Unreachable case statement: int n"
             ));

//  Finally, you can use MatchStrict!() to express a match statement with the requirement that every possible type is
//  handled. This is useful if the possible data-types will change, and you want to be warned in the future if you don't handle
//  all of them. Other than this requirement, MatchStrict!() behaves just like Match!()
    mixin(MatchStrict!(v,
             Case!("{int|char[]} a", `// Do nothing`),
             Case!("long l", `writefln("Aha! A long");`) // Commenting this line out would cause an error at compile-time:
                                                         // "Not all cases expressed in match statement"
                  ));

}



