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 helper(Args...)(Args args) { 544 return visitMember!("%s", TemplateArgs)(_value, args); 545 } 546 alias %s = helper; 547 } 548 }.format(name, name, name); 549 } 550 551 unittest { 552 struct Foo { 553 int a; 554 int b; 555 int c; 556 int d; 557 int e; 558 } 559 560 struct Bar { 561 int a; 562 real b; 563 int c() { return 1; } // getter only 564 string d; // incompatible type 565 int e(int val) { return 0; } // setter only 566 } 567 568 struct FooBar { 569 alias Store = Algebraic!(Foo, Bar); 570 Store _value; 571 572 this(T)(T t) { _value = t; } 573 574 mixin(memberVisitorCode!("a")); 575 mixin(memberVisitorCode!("b")); 576 mixin(memberVisitorCode!("c")); 577 mixin(memberVisitorCode!("d")); 578 mixin(memberVisitorCode!("e")); 579 } 580 581 FooBar fb = Foo(1); 582 583 static assert(is(typeof(fb.a()) == int)); // both are int 584 static assert(is(typeof(fb.b()) == real)); // real is common type of (int, real) 585 static assert(is(typeof(fb.c()) == int)); // field on Foo, function on Bar 586 587 static assert( is(typeof(fb.a = 5) == int )); // both are int 588 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 589 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 590 591 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 592 static assert(!is(typeof(fb.c = 5 ))); // getter only 593 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 594 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 595 } 596 597 /* 598 * Generate a string containing the `memberVisitorCode` for every name in the 599 * union of all members across SubTypes. 600 */ 601 string allVisitorCode(SubTypes...)() { 602 // allMembers will fail on a primitive types, so alias that to an empty list 603 template allMembers(T) { 604 static if (__traits(compiles, __traits(allMembers, T))) 605 alias allMembers = AliasSeq!(__traits(allMembers, T)); 606 else 607 alias allMembers = AliasSeq!(); 608 } 609 610 // ignore __ctor, __dtor, and the like 611 enum shouldExpose(string name) = (name.length < 2 || name[0..2] != "__") && 612 (name != "this"); 613 614 string str; 615 616 // generate a member to forward to each underlying member 617 foreach(member ; NoDuplicates!(staticMap!(allMembers, SubTypes))) 618 static if (shouldExpose!member) 619 str ~= memberVisitorCode!(member); 620 621 return str; 622 } 623 624 unittest { 625 struct Foo { 626 int a; 627 int b; 628 int c; 629 int d; 630 int e; 631 } 632 633 struct Bar { 634 int a; 635 real b; 636 int c() { return 1; } // getter only 637 string d; // incompatible type 638 int e(int val) { return 0; } // setter only 639 } 640 641 struct FooBar { 642 alias Store = Algebraic!(Foo, Bar); 643 Store _value; 644 645 this(T)(T t) { _value = t; } 646 647 mixin(allVisitorCode!(Foo, Bar)); 648 } 649 650 FooBar fb = Foo(1); 651 652 // getters 653 static assert( is(typeof(fb.a()) == int)); // both are int 654 static assert( is(typeof(fb.b()) == real)); // real is common type of (int, real) 655 static assert( is(typeof(fb.c()) == int)); // field on Foo, function on Bar 656 static assert( is(typeof(fb.d()) == SuperStruct!(int, string))); 657 static assert(!is(typeof(fb.e()))); // setter only 658 659 // setters 660 static assert( is(typeof(fb.a = 5) == int )); // both are int 661 static assert( is(typeof(fb.b = 5) == real)); // real is common type of (int, real) 662 static assert( is(typeof(fb.e = 5) == int )); // field on Foo, function on Bar 663 664 static assert(!is(typeof(fb.b = 5.0))); // type mismatch 665 static assert(!is(typeof(fb.c = 5 ))); // getter only 666 static assert(!is(typeof(fb.d = 5 ))); // incompatible types 667 static assert(!is(typeof(fb.d = 5.0))); // incompatible types 668 } 669 670 // make sure __ctor and __dtor don't blow things up 671 unittest { 672 struct Foo { 673 this(int i) { } 674 this(this) { } 675 ~this() { } 676 } 677 678 struct Bar { 679 this(int i) { } 680 this(this) { } 681 ~this() { } 682 } 683 684 struct FooBar { 685 alias Store = Algebraic!(Foo, Bar); 686 Store _value; 687 688 this(T)(T t) { _value = t; } 689 690 mixin(allVisitorCode!(Foo, Bar)); 691 } 692 693 FooBar fb = Foo(1); 694 }