1 /** 2 * This module provides a single type, `SuperStruct`. 3 * 4 * Authors: Ryan Roden-Corrent ($(LINK2 https://github.com/rcorre, rcorre)) 5 * License: MIT 6 * Copyright: © 2015, Ryan Roden-Corrent 7 */ 8 module superstruct; 9 10 /// Two disparate structs ... they can't be used interchangeably, right? 11 unittest { 12 import std.math, std.algorithm; 13 14 struct Square { 15 float size; 16 float area() { return size * size; } 17 } 18 19 struct Circle { 20 float r; 21 float area() { return r * r * PI; } 22 } 23 24 // Or can they? 25 alias Shape = SuperStruct!(Square, Circle); 26 27 // look! polymorphism! 28 Shape sqr = Square(2); 29 Shape cir = Circle(4); 30 Shape[] shapes = [ sqr, cir ]; 31 32 // call functions that are shared between the source types! 33 assert(shapes.map!(x => x.area).sum.approxEqual(2 * 2 + 4 * 4 * PI)); 34 } 35 36 /// Want to access fields of the underlying types? Not a problem! 37 /// Are some of them properties? Not a problem! 38 unittest { 39 struct Square { 40 int top, left, width, height; 41 } 42 43 struct Circle { 44 int radius; 45 int x, y; 46 47 auto top() { return y - radius; } 48 auto top(int val) { return y = val + radius; } 49 } 50 51 alias Shape = SuperStruct!(Square, Circle); 52 53 // if a Shape is a Circle, `top` forwards to Circle's top property 54 Shape someShape = Circle(4, 0, 0); 55 someShape.top = 6; 56 assert(someShape.top == 6); 57 58 // if a Shape is a Square, `top` forwards to Squares's top field 59 someShape = Square(0, 0, 4, 4); 60 someShape.top = 6; 61 assert(someShape.top == 6); 62 63 // Square.left is hidden, as Circle has no such member 64 static assert(!is(typeof(someShape.left))); 65 } 66 67 /// SuperStruct could be used, for example, for a generic container type: 68 unittest { 69 import std.range, std.algorithm, std.container; 70 71 alias Container(T) = SuperStruct!(SList!T, Array!T); 72 73 Container!int slist = SList!int(); 74 75 // We can call any members that are common among containers 76 slist.insert([1,2,3,4]); 77 assert(slist.front == 1); 78 79 // opSlice is supported on all the subtypes, but each returns a different type 80 // Container.opSlice will return a SuperStruct of these types 81 auto slice = slist[]; // [1,2,3,4] 82 assert(slice.front == 1); 83 slice.popFront(); // [2,3,4] 84 assert(slice.front == 2); 85 86 // as slice is a SuperStruct of range types, it still works as a range 87 slist.insert(slice); // [2,3,4] ~ [1,2,3,4] 88 assert(slist[].equal([2,3,4,1,2,3,4])); 89 } 90 91 import std.meta; 92 import std.traits; 93 import std.variant; 94 95 /** A Variant which exposes members that are common across all `SubTypes`. 96 * 97 * A `SuperStruct!(SubTypes...)` wraps an `Algebraic!(SubTypes...)`. It can 98 * hold a single value from any of its `SubTypes`. 99 * 100 * Unlike a Variant/Algebraic, `SuperStruct` exposes access to 'common' members 101 * that have compatible signatures. 102 * 103 * A member is 'common' if its name describes a public function or field on 104 * every one of `SubTypes`. A call signature for a given member is 'compatible' 105 * if, for an instance of any one of `SubTypes`, that member can be called with 106 * the provided set of arguments _and_ all such calls have a common return type. 107 * 108 * `SuperStruct` ignores members beginning with "__" (double underscore). 109 */ 110 struct SuperStruct(SubTypes...) { 111 private Algebraic!SubTypes _value; 112 113 /** 114 * Construct and populate with an initial value. 115 * Params: 116 * value = something implicitly covertible to of one of the `SubTypes`. 117 */ 118 this(V)(V value) if (is(typeof(_value = value))) { 119 _value = value; 120 } 121 122 auto opAssign(V)(V value) if (is(typeof(_value = value))) { 123 return _value = value; 124 } 125 126 /// Forward all members and template instantiations to the contained value. 127 mixin(allVisitorCode!SubTypes); 128 129 // - Basic Operator Forwarding --------------------------------- 130 131 /// Operators are forwarded to the underlying type. 132 auto opIndex(T...)(T t) { return _value.visitAny!(x => x[t]); } 133 134 /// ditto 135 auto opSlice()() { return _value.visitAny!(x => x[]); } 136 137 /// ditto 138 auto opSlice(A, B)(A a, B b) { return _value.visitAny!(x => x[a..b]); } 139 140 /// ditto 141 // TODO 142 //auto opDollar()() { return _value.visitAny!(x => x.opDollar()); } 143 144 /// ditto 145 auto opUnary(string op)() { return _value.visitAny!(x => mixin(op~"x")); } 146 147 /// ditto 148 auto opBinary(string op, T)(T other) { 149 return _value.visitAny!(x => mixin("x"~op~"other")); 150 } 151 152 /// ditto 153 auto opOpAssign(string op, T)(T other) { 154 return _value.visitAny!((ref x) => mixin("x"~op~"=other")); 155 } 156 157 /// ditto 158 bool opEquals(T)(T other) { 159 return _value.visitAny!(x => x == other); 160 } 161 162 // - Operator Forwarding between SuperStructs ----------------- 163 164 /** 165 * Perform a binary operation between two superstructs. 166 * 167 * Only possible if such an operation is supported between any of the types 168 * in either of the SuperStructs. 169 */ 170 auto opBinary(string op, T : SuperStruct!V, V...)(T other) { 171 return _value.visitAny!(x => 172 other._value.visitAny!(y => mixin("x"~op~"y"))); 173 } 174 175 /// ditto 176 auto opOpAssign(string op, T : SuperStruct!V, V...)(T other) { 177 return _value.visitAny!((ref x) => 178 other._value.visitAny!(( y) => mixin("x"~op~"=y"))); 179 } 180 181 /** 182 * Compare one `SuperStruct` to another of the same type. 183 * 184 * Invokes opEquals if the contained types are comparable. 185 * Otherwise returns false. 186 */ 187 auto opEquals(typeof(this) other) { 188 bool helper(A, B)(A a, B b) { 189 static if (is(typeof(a == b))) 190 return a == b; 191 else 192 return false; 193 } 194 195 return _value.visitAny!(x => 196 other._value.visitAny!(y => helper(x, y))); 197 } 198 } 199 200 /// If all types have a matching field, it gets exposed: 201 unittest { 202 struct Foo { int a; } 203 struct Bar { int a; } 204 auto foobar = SuperStruct!(Foo, Bar)(Foo(1)); 205 foobar.a = 5; 206 assert(foobar.a == 5); 207 } 208 209 /// If all types have a matching method, all compatible overloads are exposed: 210 unittest { 211 struct Foo { 212 int fun(int i) { return i; } 213 int fun(int a, int b) { return a + b; } 214 } 215 struct Bar { 216 int fun(int i) { return i; } 217 int fun(int a, int b) { return a + b; } 218 int fun(int a, int b, int c) { return a + b + c; } 219 } 220 221 auto foobar = SuperStruct!(Foo, Bar)(Foo()); 222 assert(foobar.fun(1) == 1); 223 assert(foobar.fun(1, 2) == 3); 224 assert(!__traits(compiles, foobar.fun(1,2,3))); // no such overload on Foo 225 } 226 227 /// If a name is a field on one type and a method on another, it is exposed: 228 unittest { 229 struct Foo { int a; } 230 struct Bar { 231 private int _a; 232 int a() { return _a; } 233 int a(int val) { return _a = val; } 234 } 235 236 auto foo = SuperStruct!(Foo, Bar)(Foo()); 237 foo.a = 5; // sets Foo.a 238 assert(foo.a == 5); // gets Foo.a 239 240 auto bar = SuperStruct!(Foo, Bar)(Bar()); 241 bar.a = 5; // invokes Bar.a(int val) 242 assert(bar.a == 5); // invokes Bar.a() 243 } 244 245 /// Templated members can be forwarded too: 246 unittest { 247 struct Foo { 248 int val; 249 auto transmorgrify(alias fn1, alias fn2)() { 250 return fn2(fn2(val)); 251 } 252 } 253 254 struct Bar { 255 auto transmorgrify(alias fn1, alias fn2)() { return 0; } 256 } 257 258 static auto add1 = (int a) => a + 1; 259 260 alias FooBar = SuperStruct!(Foo, Bar); 261 262 FooBar f = Foo(3); 263 assert(f.transmorgrify!(add1, add1) == 5); // 3 + 1 + 1 264 265 FooBar b = Bar(); 266 assert(b.transmorgrify!(add1, add1) == 0); 267 } 268 269 /// Operators get forwarded to the underlying type 270 unittest { 271 struct Foo { 272 auto opSlice() { return [1,2,3]; } 273 } 274 275 struct Bar { 276 auto opSlice() { return [4,5,6]; } 277 } 278 279 SuperStruct!(Foo, Bar) fb = Foo(); 280 assert(fb[] == [1,2,3]); 281 } 282 283 /// SuperStructs of the same type can be compared: 284 unittest { 285 struct A { int i; } 286 struct B { int i; } 287 struct C { int i; bool opEquals(T)(T other) { return other.i == i; } } 288 289 SuperStruct!(A, B, C) a0 = A(0); 290 SuperStruct!(A, B, C) a1 = A(1); 291 SuperStruct!(A, B, C) b0 = B(0); 292 SuperStruct!(A, B, C) c0 = C(0); 293 294 assert(a0 == a0); // same type, same value 295 assert(a0 != a1); // same type, different value 296 assert(a0 != b0); // incomparable types return false 297 assert(a0 == c0); // different but comparable types 298 299 // SuperStructs with different sets of source types are not comparable, even 300 // if the types they happen to contain at the moment are. 301 SuperStruct!(A, B) different = A(0); 302 static assert(!__traits(compiles, different == a0)); 303 } 304 305 /// If members have common signatures but no common return type, the exposed 306 /// member returns a `SuperStruct` of the possible return types. 307 unittest { 308 struct A { auto fun() { return 1; } } 309 struct B { auto fun() { return "hi"; } } 310 311 SuperStruct!(A, B) a = A(); 312 SuperStruct!(A, B) b = B(); 313 314 assert(a.fun == SuperStruct!(int, string)(1)); 315 assert(b.fun == SuperStruct!(int, string)("hi")); 316 } 317 318 /** 319 * Wrap one of several values in a `SuperStruct`. 320 * 321 * This can be used to return a value from one of several different types. 322 * Similar to `std.range.chooseAmong`, but for a broader range of types. 323 * 324 * Returns: A `SuperStruct!T` constructed from the value at `index`. 325 */ 326 auto pick(T...)(size_t index, T values) { 327 foreach(idx, val ; values) 328 if (idx == index) 329 return SuperStruct!T(val); 330 331 assert(0, "index not in range of provided values"); 332 } 333 334 /// `pick` is useful for something that is a floor wax _and_ a dessert topping: 335 unittest { 336 struct FloorWax { string itIs() { return "a floor wax!"; } } 337 struct DessertTopping { string itIs() { return "a dessert topping!"; } } 338 339 auto shimmer(bool hungry) { 340 return pick(hungry, FloorWax(), DessertTopping()); 341 } 342 343 assert(shimmer(false).itIs == "a floor wax!"); 344 assert(shimmer(true ).itIs == "a dessert topping!"); 345 } 346 347 private: 348 /* 349 * Try to invoke `member` with the provided `args` for every `AllowedType`. 350 * Compiles only if such a call is possible on every type. 351 * Compiles only if the return values of all such calls share a common type. 352 */ 353 template visitMember(string member, TemplateArgs...) { 354 // if we got some explicit compile time args, we need to pass them along. 355 // otherwise, omit the !() as an empty template arg list can cause issues 356 static if (TemplateArgs.length) 357 enum bang = "!(TemplateArgs)"; 358 else 359 enum bang = ""; 360 361 // this nested function allows two sets of compile-time args: 362 // one from the enclosing template scope and one for the variadic args of 363 auto visitMember(V, Args...)(ref V var, Args args) { 364 static if (Args.length == 0) // field or 'getter' (no-args function) 365 enum expression = "x."~member~bang; 366 else static if (Args.length == 1) // field or 'setter' (1-arg function) 367 enum expression = "x."~member~bang~"=args[0]"; 368 else // 2+ arg function 369 enum expression = "x."~member~bang~"(args)"; 370 371 return var.visitAny!((ref x) => mixin(expression)); 372 } 373 } 374 375 unittest { 376 struct Foo { 377 int num; 378 379 string shout() { return "hi!"; } 380 void assign(int val) { num = val; } 381 void assign(int val1, int val2) { num = val1 + val2; } 382 383 void set(T)(T val) { num = val; } // templated setter 384 } 385 386 struct Bar { 387 int num, othernum; 388 389 string shout() { return "bye!"; } 390 void assign(int val) { num = val + 1; } 391 void assign(int val1, int val2) { num = val1 + val2 + 1; } 392 void assign(int val1, int val2, int val3) { num = val1 + val2 + val3; } 393 394 void set(T)(T val) { num = val; } 395 } 396 397 alias Thing = Algebraic!(Foo, Bar); 398 399 Thing foo = Foo(4); 400 Thing bar = Bar(5, 6); 401 402 assert(visitMember!"num"(foo) == 4); 403 assert(visitMember!"num"(bar) == 5); 404 405 assert(visitMember!"num"(foo, 5) == 5); 406 assert(visitMember!"num"(foo) == 5); 407 408 assert(visitMember!"shout"(foo) == "hi!"); 409 assert(visitMember!"shout"(bar) == "bye!"); 410 assert(visitMember!"shout"(bar) == "bye!"); 411 412 visitMember!"assign"(foo, 2); 413 assert(visitMember!"num"(foo) == 2); 414 415 visitMember!"assign"(bar, 2); 416 assert(visitMember!"num"(bar) == 3); // bar adds 1 417 418 visitMember!"assign"(foo, 2, 6); 419 assert(visitMember!"num"(foo) == 8); 420 421 visitMember!"set"(foo, 9); 422 assert(visitMember!"num"(foo) == 9); 423 424 // field 'othernum' only exists on bar 425 static assert(!__traits(compiles, visitMember!"othernum"(bar))); 426 static assert(!__traits(compiles, visitMember!"othernum"(bar))); 427 428 // 3-param overload of 'assign' only exists on Bar 429 static assert(!__traits(compiles, visitMember!"assign"(bar, 2, 6, 8))); 430 } 431 432 // pass along template arguments 433 unittest { 434 struct Foo { 435 int val; 436 437 auto noargs(alias fn)() { return fn(val); } 438 auto onearg(alias fn)(int i) { return fn(i); } 439 auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); } 440 441 auto onetype(T)(T arg) { return val + arg; } 442 auto twotype(T, V)(T t, V v) { return val + t + v; } 443 } 444 445 struct Bar { 446 int val; 447 448 auto noargs(alias fn)() { return fn(val); } 449 auto onearg(alias fn)(int i) { return fn(i); } 450 auto twofns(alias fn1, alias fn2)(int i) { return fn2(fn1(i)); } 451 452 auto onetype(T)(T arg) { return val + arg; } 453 auto twotype(T, V)(T t, V v) { return val + t + v; } 454 } 455 456 alias FooBar = Algebraic!(Foo, Bar); 457 FooBar fb = Foo(3); 458 459 // need to use a static fn here due to unrelated issue: 460 // cannot use local 'add1' as parameter to non-global template 461 static auto add1 = (int a) => a + 1; 462 static auto add2 = (int a) => a + 2; 463 464 assert(fb.visitMember!("noargs", add1)() == 4); // 3 + 1 465 assert(fb.visitMember!("onearg", add1)(5) == 6); // 5 + 1 466 assert(fb.visitMember!("twofns", add1, add2)(5) == 8); // 5 + 1 + 2 467 468 // implicit type args 469 assert(fb.visitMember!("onetype")(5) == 8); // 3 + 5 470 assert(fb.visitMember!("twotype")(5, 7) == 15); // 3 + 5 + 7 471 assert(fb.visitMember!("twotype")(5f, 7f) == 15f); // 3 + 5 + 7 472 473 // explicit type args 474 assert(fb.visitMember!("onetype", int)(5) == 8); // 3 + 5 475 assert(fb.visitMember!("twotype", int)(5, 7) == 15); // 3 + 5 + 7 476 assert(fb.visitMember!("twotype", float, float)(5, 7) == 15f); // 3 + 5 + 7 477 478 // only specify some type args 479 assert(fb.visitMember!("twotype", float)(5, 7) == 15f); // 3 + 5 + 7 480 } 481 482 auto visitAny(alias fn, V)(ref V var) { 483 // Collect the possible return types for this function across the subtypes 484 alias returnType(T) = typeof(fn(*var.peek!T)); 485 alias AllTypes = staticMap!(returnType, V.AllowedTypes); 486 487 enum allVoid = EraseAll!(void, AllTypes).length == 0; 488 enum allCommon = !is(CommonType!AllTypes == void); 489 490 foreach(T ; var.AllowedTypes) 491 if (auto ptr = var.peek!T) { 492 static if (allCommon || allVoid) 493 return fn(*ptr); 494 else static if (!allVoid) 495 return SuperStruct!AllTypes(fn(*ptr)); 496 else 497 static assert(0, "Cannot mix void and non-void return types"); 498 } 499 500 assert(0, "Variant holds no value"); 501 } 502 503 unittest { 504 struct Foo { 505 auto opSlice() { return [1,2,3]; } 506 auto opBinary(string op)(string val) { return "foo"~op~"val"; } 507 } 508 509 struct Bar { 510 auto opSlice() { return [4,5,6]; } 511 auto opBinary(string op)(string val) { return "foo"~op~"val"; } 512 } 513 514 Algebraic!(Foo, Bar) fb = Foo(); 515 assert(fb.visitAny!(x => x[]) == [1,2,3]); 516 } 517 518 unittest { 519 struct One { auto opEquals(int i) { return i == 1; } } 520 struct Two { auto opEquals(int i) { return i == 2; } } 521 522 Algebraic!(One, Two) one = One(); 523 Algebraic!(One, Two) two = Two(); 524 525 assert( one.visitAny!(x => x == 1)); 526 assert(!one.visitAny!(x => x == 2)); 527 assert(!two.visitAny!(x => x == 1)); 528 assert( two.visitAny!(x => x == 2)); 529 } 530 531 /* 532 * Generate a templated function to expose access to a given member across all 533 * types that could be stored in the Variant `_value`. 534 * For any given call signature, this template will instantiate only if the 535 * matching member on every subtype is callable with such a signature _and_ if 536 * all such calls have a common return type. 537 */ 538 string memberVisitorCode(string name)() { 539 import std.string : format; 540 541 return q{ 542 template %s(TemplateArgs...) { 543 auto %s(Args...)(Args args) { 544 return visitMember!("%s", TemplateArgs)(_value, args); 545 } 546 } 547 }.format(name, name, name); 548 } 549 550 unittest { 551 struct Foo { 552 int a; 553 int b; 554 int c; 555 int d; 556 int e; 557 } 558 559 struct Bar { 560 int a; 561 real b; 562 int c() { return 1; } // getter only 563 string d; // incompatible type 564 int e(int val) { return 0; } // setter only 565 } 566 567 struct FooBar { 568 alias Store = Algebraic!(Foo, Bar); 569 Store _value; 570 571 this(T)(T t) { _value = t; } 572 573 mixin(memberVisitorCode!("a")); 574 mixin(memberVisitorCode!("b")); 575 mixin(memberVisitorCode!("c")); 576 mixin(memberVisitorCode!("d")); 577 mixin(memberVisitorCode!("e")); 578 } 579 580 FooBar fb = Foo(1); 581 582 static assert(is(typeof(fb.a()) == int)); // both are int 583 static assert(is(typeof(fb.b()) == real)); // real is common type of (int, real) 584 static assert(is(typeof(fb.c()) == int)); // field on Foo, function on Bar 585 586 static assert( is(typeof(fb.a = 5) == int )); // both are int 587 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 588 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 589 590 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 591 static assert(!is(typeof(fb.c = 5 ))); // getter only 592 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 593 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 594 } 595 596 /* 597 * Generate a string containing the `memberVisitorCode` for every name in the 598 * union of all members across SubTypes. 599 */ 600 string allVisitorCode(SubTypes...)() { 601 // allMembers will fail on a primitive types, so alias that to an empty list 602 template allMembers(T) { 603 static if (__traits(compiles, __traits(allMembers, T))) 604 alias allMembers = AliasSeq!(__traits(allMembers, T)); 605 else 606 alias allMembers = AliasSeq!(); 607 } 608 609 // ignore hidden members like __ctor, this, and operators 610 enum shouldExpose(string name) = (name.length < 2 || name[0..2] != "__") && 611 name != "this" && 612 name != "opUnary" && 613 name != "opBinary" && 614 name != "opCast" && 615 name != "opEquals" && 616 name != "opCmp" && 617 name != "opCall" && 618 name != "opAssign" && 619 name != "opIndex" && 620 name != "opSlice" && 621 name != "opDollar"; 622 623 string str; 624 625 // generate a member to forward to each underlying member 626 foreach(member ; NoDuplicates!(staticMap!(allMembers, SubTypes))) 627 static if (shouldExpose!member) 628 str ~= memberVisitorCode!(member); 629 630 return str; 631 } 632 633 unittest { 634 struct Foo { 635 int a; 636 int b; 637 int c; 638 int d; 639 int e; 640 } 641 642 struct Bar { 643 int a; 644 real b; 645 int c() { return 1; } // getter only 646 string d; // incompatible type 647 int e(int val) { return 0; } // setter only 648 } 649 650 struct FooBar { 651 alias Store = Algebraic!(Foo, Bar); 652 Store _value; 653 654 this(T)(T t) { _value = t; } 655 656 mixin(allVisitorCode!(Foo, Bar)); 657 } 658 659 FooBar fb = Foo(1); 660 661 // getters 662 static assert( is(typeof(fb.a()) == int)); // both are int 663 static assert( is(typeof(fb.b()) == real)); // real is common type of (int, real) 664 static assert( is(typeof(fb.c()) == int)); // field on Foo, function on Bar 665 static assert( is(typeof(fb.d()) == SuperStruct!(int, string))); 666 static assert(!is(typeof(fb.e()))); // setter only 667 668 // setters 669 static assert( is(typeof(fb.a = 5) == int )); // both are int 670 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 671 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 672 673 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 674 static assert(!is(typeof(fb.c = 5 ))); // getter only 675 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 676 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 677 } 678 679 // make sure __ctor and __dtor don't blow things up 680 unittest { 681 struct Foo { 682 this(int i) { } 683 this(this) { } 684 ~this() { } 685 } 686 687 struct Bar { 688 this(int i) { } 689 this(this) { } 690 ~this() { } 691 } 692 693 struct FooBar { 694 alias Store = Algebraic!(Foo, Bar); 695 Store _value; 696 697 this(T)(T t) { _value = t; } 698 699 mixin(allVisitorCode!(Foo, Bar)); 700 } 701 702 FooBar fb = Foo(1); 703 }