1 /++ 2 This module contains the [meldInto] functions; functions that take two 3 structs or classes of the same type and combine them, creating a resulting 4 object with the union of the members of both parents. Array and associative 5 array variants exist too. 6 7 Example: 8 --- 9 struct Foo 10 { 11 string abc; 12 string def; 13 int i; 14 float f; 15 double d; 16 } 17 18 Foo f1; // = new Foo; 19 f1.abc = "ABC"; 20 f1.def = "DEF"; 21 22 Foo f2; // = new Foo; 23 f2.abc = "this won't get copied"; 24 f2.def = "neither will this"; 25 f2.i = 42; 26 f2.f = 3.14f; 27 28 f2.meldInto(f1); 29 30 with (f1) 31 { 32 import std.math : isNaN; 33 34 assert(abc == "ABC"); 35 assert(def == "DEF"); 36 assert(i == 42); 37 assert(f == 3.14f); 38 assert(d.isNaN); 39 } 40 --- 41 42 Copyright: [JR](https://github.com/zorael) 43 License: [Boost Software License 1.0](https://www.boost.org/users/license.html) 44 45 Authors: 46 [JR](https://github.com/zorael) 47 +/ 48 module lu.meld; 49 50 private: 51 52 import lu.traits : isMerelyArray; 53 import std.traits : isAggregateType, isArray, isAssociativeArray, isMutable; 54 55 public: 56 57 58 /++ 59 To what extent a source should overwrite a target when melding. 60 +/ 61 enum MeldingStrategy 62 { 63 /++ 64 Takes care not to overwrite settings when either the source or the 65 target is `.init`. 66 +/ 67 conservative, 68 69 /++ 70 Only considers the `init`-ness of the source, so as not to overwrite 71 things with empty strings, but otherwise always considers the source to 72 trump the target. 73 +/ 74 aggressive, 75 76 /++ 77 Works like aggressive but also always overwrites bools, regardless of 78 falseness. 79 +/ 80 overwriting, 81 } 82 83 84 /++ 85 UDA conveying that this member's value cannot or should not be melded. 86 +/ 87 enum Unmeldable; 88 89 90 // meldInto 91 /++ 92 Takes two structs or classes of the same type and melds them together, 93 making the members a union of the two. 94 95 In the case of classes it only overwrites members in `intoThis` that are 96 `typeof(member).init`, so only unset members get their values overwritten by 97 the melding class. It also does not work with static members. 98 99 In the case of structs it also overwrites members that still have their 100 default values, in cases where such is applicable. 101 102 Supply a template parameter [MeldingStrategy] to decide to which extent 103 values are overwritten. 104 105 Example: 106 --- 107 struct Foo 108 { 109 string abc; 110 int def; 111 bool b = true; 112 } 113 114 Foo foo, bar; 115 foo.abc = "from foo" 116 foo.b = false; 117 bar.def = 42; 118 foo.meldInto(bar); 119 120 assert(bar.abc == "from foo"); 121 assert(bar.def == 42); 122 assert(!bar.b); // false overwrote default value true 123 --- 124 125 Params: 126 strategy = To what extent the source object should overwrite set 127 (non-`init`) values in the receiving object. 128 meldThis = Object to meld (source). 129 intoThis = Reference to object to meld (target). 130 +/ 131 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, QualThing, Thing) 132 (auto ref QualThing meldThis, ref Thing intoThis) 133 if (isAggregateType!Thing && is(QualThing : Thing) && isMutable!Thing) 134 { 135 static if (is(Thing == struct) && (strategy == MeldingStrategy.conservative)) 136 { 137 import std.traits : hasUnsharedAliasing; 138 139 if (meldThis == Thing.init) 140 { 141 // We're merging an .init with something, and .init does not have 142 // any special default values. Nothing would get melded, so exit early. 143 return; 144 } 145 146 static if (!hasUnsharedAliasing!Thing) 147 { 148 if (intoThis == Thing.init) 149 { 150 // Likewise we're merging into an .init, so just fast-path overwrite. 151 intoThis = meldThis; 152 return; 153 } 154 } 155 } 156 157 foreach (immutable i, ref _; intoThis.tupleof) 158 { 159 import std.traits : isType; 160 161 static if (!isType!(intoThis.tupleof[i])) 162 { 163 import lu.traits : udaIndexOf; 164 import std.traits : 165 isAggregateType, 166 isArray, 167 isAssignable, 168 isPointer, 169 isSomeString; 170 171 alias T = typeof(intoThis.tupleof[i]); 172 173 static if (udaIndexOf!(intoThis.tupleof[i], Unmeldable) != -1) 174 { 175 // Do nothing 176 } 177 else static if (isAggregateType!T) 178 { 179 // Recurse 180 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]); 181 } 182 else static if (isAssignable!T) 183 { 184 /+ 185 Overwriting strategy overwrites everything except where the 186 source is clearly `.init`. 187 188 Aggressive strategy works like overwriting except it doesn't 189 blindly overwrite struct bools. 190 +/ 191 static if ( 192 (strategy == MeldingStrategy.overwriting) || 193 (strategy == MeldingStrategy.aggressive)) 194 { 195 static if (is(T == float) || is(T == double)) 196 { 197 import std.math : isNaN; 198 199 if (!meldThis.tupleof[i].isNaN) 200 { 201 intoThis.tupleof[i] = meldThis.tupleof[i]; 202 } 203 } 204 else static if (is(T == bool)) 205 { 206 static if (strategy == MeldingStrategy.overwriting) 207 { 208 // Non-discriminately overwrite bools 209 intoThis.tupleof[i] = meldThis.tupleof[i]; 210 } 211 else static if (strategy == MeldingStrategy.aggressive) 212 { 213 static if (is(Thing == class)) 214 { 215 // We cannot tell whether or not it has the same value as 216 // `Thing.init` does, as it would need to be instantiated. 217 // Assume overwrite? 218 intoThis.tupleof[i] = meldThis.tupleof[i]; 219 } 220 else 221 { 222 if (intoThis.tupleof[i] == Thing.init.tupleof[i]) 223 { 224 intoThis.tupleof[i] = meldThis.tupleof[i]; 225 } 226 } 227 } 228 else 229 { 230 static assert(0, "Logic error; unexpected `MeldingStrategy` " ~ 231 "passed to struct/class `meldInto`"); 232 } 233 } 234 else static if (isArray!T && !isSomeString!T) 235 { 236 // Pass on to array melder 237 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]); 238 } 239 else static if (isAssociativeArray!T) 240 { 241 // Pass on to AA melder 242 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]); 243 } 244 else static if (isPointer!T) 245 { 246 // Aggressive and/or overwriting, so just overwrite the pointer? 247 intoThis.tupleof[i] = meldThis.tupleof[i]; 248 } 249 else static if (is(Thing == class)) 250 { 251 // Can't compare with Thing.init.tupleof[i] 252 intoThis.tupleof[i] = meldThis.tupleof[i]; 253 } 254 else 255 { 256 if (meldThis.tupleof[i] != Thing.init.tupleof[i]) 257 { 258 intoThis.tupleof[i] = meldThis.tupleof[i]; 259 } 260 } 261 } 262 /+ 263 Conservative strategy takes care not to overwrite members 264 with non-`init` values. 265 +/ 266 else static if (strategy == MeldingStrategy.conservative) 267 { 268 static if (is(T == float) || is(T == double)) 269 { 270 import std.math : isNaN; 271 272 if (intoThis.tupleof[i].isNaN) 273 { 274 intoThis.tupleof[i] = meldThis.tupleof[i]; 275 } 276 } 277 else static if (is(T == enum)) 278 { 279 if (meldThis.tupleof[i] > intoThis.tupleof[i]) 280 { 281 intoThis.tupleof[i] = meldThis.tupleof[i]; 282 } 283 } 284 else static if (is(T == string[])) 285 { 286 import std.algorithm.searching : canFind; 287 288 if (!intoThis.tupleof[i].canFind(meldThis.tupleof[i])) 289 { 290 intoThis.tupleof[i] ~= meldThis.tupleof[i]; 291 } 292 } 293 else static if (isArray!T && !isSomeString!T) 294 { 295 // Pass on to array melder 296 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]); 297 } 298 else static if (isAssociativeArray!T) 299 { 300 // Pass on to AA melder 301 meldThis.tupleof[i].meldInto!strategy(intoThis.tupleof[i]); 302 } 303 else static if (isPointer!T) 304 { 305 // Conservative, so check if null and overwrite if so 306 if (!intoThis.tupleof[i]) intoThis.tupleof[i] = meldThis.tupleof[i]; 307 } 308 else static if (is(T == bool)) 309 { 310 static if (is(Thing == class)) 311 { 312 /+ 313 We cannot tell whether or not it has the same value as 314 `Thing.init` does, as it would need to be instantiated. 315 Assume overwrite? 316 +/ 317 intoThis.tupleof[i] = meldThis.tupleof[i]; 318 } 319 else 320 { 321 if (intoThis.tupleof[i] == Thing.init.tupleof[i]) 322 { 323 intoThis.tupleof[i] = meldThis.tupleof[i]; 324 } 325 } 326 } 327 else 328 { 329 /+ 330 This is tricksy for bools. A value of false could be 331 false, or merely unset. If we're not overwriting, 332 let whichever side is true win out? 333 +/ 334 static if (is(Thing == class)) 335 { 336 if (intoThis.tupleof[i] == T.init) 337 { 338 intoThis.tupleof[i] = meldThis.tupleof[i]; 339 } 340 } 341 else 342 { 343 if ((intoThis.tupleof[i] == T.init) || 344 (intoThis.tupleof[i] == Thing.init.tupleof[i])) 345 { 346 intoThis.tupleof[i] = meldThis.tupleof[i]; 347 } 348 } 349 } 350 } 351 } 352 else 353 { 354 /*import std.format : format; 355 import std.traits : Unqual; 356 357 enum pattern = "`%s` `%s.%s` is not meldable!"; 358 enum message = pattern.format( 359 Unqual!T.stringof, 360 Unqual!QualThing.stringof, 361 __traits(identifier, meldThis.tupleof[i])); 362 static assert(0, message);*/ 363 } 364 } 365 } 366 } 367 368 /// 369 unittest 370 { 371 import std.conv : to; 372 373 static struct TestFoo 374 { 375 string abc; 376 string def; 377 int i; 378 float f; 379 double d; 380 int[string] aa; 381 int[] arr; 382 int* ip; 383 384 void blah() {} 385 386 const string kek; 387 immutable bool bur; 388 389 this(bool bur) 390 { 391 kek = "uden lo"; 392 this.bur = bur; 393 } 394 } 395 396 TestFoo f1; // = new TestFoo; 397 f1.abc = "ABC"; 398 f1.def = "DEF"; 399 f1.aa = [ "abc" : 123, "ghi" : 789 ]; 400 f1.arr = [ 1, 0, 3, 0, 5 ]; 401 402 TestFoo f2; // = new TestFoo; 403 f2.abc = "this won't get copied"; 404 f2.def = "neither will this"; 405 f2.i = 42; 406 f2.f = 3.14f; 407 f2.aa = [ "abc" : 999, "def" : 456 ]; 408 f2.arr = [ 0, 2, 0, 4 ]; 409 410 f2.meldInto(f1); 411 412 with (f1) 413 { 414 import std.math : isNaN; 415 416 assert((abc == "ABC"), abc); 417 assert((def == "DEF"), def); 418 assert((i == 42), i.to!string); 419 assert((f == 3.14f), f.to!string); 420 assert(d.isNaN, d.to!string); 421 assert((aa == [ "abc" : 123, "def" : 456, "ghi" : 789 ]), aa.to!string); 422 assert((arr == [ 1, 2, 3, 4, 5 ]), arr.to!string); 423 } 424 425 TestFoo f3; // new TestFoo; 426 f3.abc = "abc"; 427 f3.def = "def"; 428 f3.i = 100_135; 429 f3.f = 99.9f; 430 f3.aa = [ "abc" : 123, "ghi" : 789 ]; 431 f3.arr = [ 1, 0, 3, 0, 5 ]; 432 433 TestFoo f4; // new TestFoo; 434 f4.abc = "OVERWRITTEN"; 435 f4.def = "OVERWRITTEN TOO"; 436 f4.i = 0; 437 f4.f = 0.1f; 438 f4.d = 99.999; 439 f4.aa = [ "abc" : 999, "def" : 456 ]; 440 f4.arr = [ 9, 2, 0, 4 ]; 441 442 f4.meldInto!(MeldingStrategy.aggressive)(f3); 443 444 with (f3) 445 { 446 static if (__VERSION__ >= 2091) 447 { 448 import std.math : isClose; 449 } 450 else 451 { 452 import std.math : approxEqual; 453 alias isClose = approxEqual; 454 } 455 456 assert((abc == "OVERWRITTEN"), abc); 457 assert((def == "OVERWRITTEN TOO"), def); 458 assert((i == 100_135), i.to!string); // 0 is int.init 459 assert((f == 0.1f), f.to!string); 460 assert(isClose(d, 99.999), d.to!string); 461 assert((aa == [ "abc" : 999, "def" : 456, "ghi" : 789 ]), aa.to!string); 462 assert((arr == [ 9, 2, 3, 4, 5 ]), arr.to!string); 463 } 464 465 // Overwriting is just aggressive but always overwrites bools. 466 467 struct User 468 { 469 enum Class { anyone, blacklist, whitelist, admin } 470 string nickname; 471 string alias_; 472 string ident; 473 string address; 474 string login; 475 bool special; 476 Class class_; 477 } 478 479 User one; 480 with (one) 481 { 482 nickname = "foobar"; 483 ident = "NaN"; 484 address = "herpderp.net"; 485 special = false; 486 class_ = User.Class.whitelist; 487 } 488 489 User two; 490 with (two) 491 { 492 nickname = "foobar^"; 493 alias_ = "FooBar"; 494 address = "asdf.org"; 495 login = "kamelusu"; 496 special = true; 497 class_ = User.Class.blacklist; 498 } 499 500 //import lu.conv : Enum; 501 502 User twoCopy = two; 503 504 one.meldInto!(MeldingStrategy.conservative)(two); 505 with (two) 506 { 507 assert((nickname == "foobar^"), nickname); 508 assert((alias_ == "FooBar"), alias_); 509 assert((ident == "NaN"), ident); 510 assert((address == "asdf.org"), address); 511 assert((login == "kamelusu"), login); 512 assert(special); 513 assert(class_ == User.Class.whitelist);//, Enum!(User.Class).toString(class_)); 514 } 515 516 one.class_ = User.Class.blacklist; 517 518 one.meldInto!(MeldingStrategy.overwriting)(twoCopy); 519 with (twoCopy) 520 { 521 assert((nickname == "foobar"), nickname); 522 assert((alias_ == "FooBar"), alias_); 523 assert((ident == "NaN"), ident); 524 assert((address == "herpderp.net"), address); 525 assert((login == "kamelusu"), login); 526 assert(!special); 527 assert(class_ == User.Class.blacklist);//, Enum!(User.Class).toString(class_)); 528 } 529 530 struct EnumThing 531 { 532 enum Enum { unset, one, two, three } 533 Enum enum_; 534 } 535 536 EnumThing e1; 537 EnumThing e2; 538 e2.enum_ = EnumThing.Enum.three; 539 assert(e1.enum_ == EnumThing.Enum.init);//, Enum!(EnumThing.Enum).toString(e1.enum_)); 540 e2.meldInto(e1); 541 assert(e1.enum_ == EnumThing.Enum.three);//, Enum!(EnumThing.Enum).toString(e1.enum_)); 542 543 struct WithArray 544 { 545 string[] arr; 546 } 547 548 WithArray w1, w2; 549 w1.arr = [ "arr", "matey", "I'ma" ]; 550 w2.arr = [ "pirate", "stereotype", "unittest" ]; 551 w2.meldInto(w1); 552 assert((w1.arr == [ "arr", "matey", "I'ma", "pirate", "stereotype", "unittest" ]), w1.arr.to!string); 553 554 WithArray w3, w4; 555 w3.arr = [ "arr", "matey", "I'ma" ]; 556 w4.arr = [ "arr", "matey", "I'ma" ]; 557 w4.meldInto(w3); 558 assert((w3.arr == [ "arr", "matey", "I'ma" ]), w3.arr.to!string); 559 560 struct Server 561 { 562 string address; 563 } 564 565 struct Bot 566 { 567 string nickname; 568 Server server; 569 } 570 571 Bot b1, b2; 572 b1.nickname = "foobar"; 573 b1.server.address = "freenode.net"; 574 575 assert(!b2.nickname.length, b2.nickname); 576 assert(!b2.server.address.length, b2.nickname); 577 b1.meldInto(b2); 578 assert((b2.nickname == "foobar"), b2.nickname); 579 assert((b2.server.address == "freenode.net"), b2.server.address); 580 581 b2.nickname = "harbl"; 582 b2.server.address = "rizon.net"; 583 584 b2.meldInto!(MeldingStrategy.aggressive)(b1); 585 assert((b1.nickname == "harbl"), b1.nickname); 586 assert((b1.server.address == "rizon.net"), b1.server.address); 587 588 class Class 589 { 590 static int i; 591 string s; 592 bool b; 593 } 594 595 Class abc = new Class; 596 abc.i = 42; 597 abc.s = "some string"; 598 abc.b = true; 599 600 Class def = new Class; 601 def.s = "other string"; 602 abc.meldInto(def); 603 604 assert((def.i == 42), def.i.to!string); 605 assert((def.s == "other string"), def.s); 606 assert(def.b); 607 608 abc.meldInto!(MeldingStrategy.aggressive)(def); 609 assert((def.s == "some string"), def.s); 610 611 struct Bools 612 { 613 bool a = true; 614 bool b = false; 615 } 616 617 Bools bools1, bools2, inverted, backupInverted; 618 619 bools2.a = false; 620 621 inverted.a = false; 622 inverted.b = true; 623 backupInverted = inverted; 624 625 bools2.meldInto(bools1); 626 assert(!bools1.a); 627 assert(!bools1.b); 628 629 bools2.meldInto(inverted); 630 assert(!inverted.a); 631 assert(inverted.b); 632 inverted = backupInverted; 633 634 bools2.meldInto!(MeldingStrategy.overwriting)(inverted); 635 assert(!inverted.a); 636 assert(!inverted.b); 637 inverted = backupInverted; 638 639 struct Asdf 640 { 641 string nickname = "sadf"; 642 string server = "asdf.net"; 643 } 644 645 Asdf a, b; 646 a.server = "a"; 647 b.server = "b"; 648 b.meldInto!(MeldingStrategy.aggressive)(a); 649 assert((a.server == "b"), a.server); 650 651 a.server = "a"; 652 b.server = Asdf.init.server; 653 b.meldInto!(MeldingStrategy.aggressive)(a); 654 assert((a.server == "a"), a.server); 655 656 struct Blah 657 { 658 int yes = 42; 659 @Unmeldable int no = 24; 660 } 661 662 Blah blah1, blah2; 663 blah1.yes = 5; 664 blah1.no = 42; 665 blah1.meldInto!(MeldingStrategy.aggressive)(blah2); 666 assert((blah2.yes == 5), blah2.yes.to!string); 667 assert((blah2.no == 24), blah2.no.to!string); 668 } 669 670 671 // meldInto (array) 672 /++ 673 Takes two arrays and melds them together, making a union of the two. 674 675 It only overwrites members that are `T.init`, so only unset 676 fields get their values overwritten by the melding array. Supply a 677 template parameter [MeldingStrategy.aggressive] to make it overwrite if the 678 melding array's field is not `T.init`. Furthermore use 679 [MeldingStrategy.overwriting] if working with bool members. 680 681 Example: 682 --- 683 int[] arr1 = [ 1, 2, 3, 0, 0, 0 ]; 684 int[] arr2 = [ 0, 0, 0, 4, 5, 6 ]; 685 arr1.meldInto!(MeldingStrategy.conservative)(arr2); 686 687 assert(arr2 == [ 1, 2, 3, 4, 5, 6 ]); 688 --- 689 690 Params: 691 strategy = To what extent the source object should overwrite set 692 (non-`init`) values in the receiving object. 693 meldThis = Array to meld (source). 694 intoThis = Reference to the array to meld (target). 695 +/ 696 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, Array1, Array2) 697 (auto ref Array1 meldThis, ref Array2 intoThis) pure nothrow 698 if (isMerelyArray!Array1 && isMerelyArray!Array2 && isMutable!Array2) 699 { 700 import std.traits : isDynamicArray, isStaticArray; 701 702 static if (isDynamicArray!Array2) 703 { 704 if (!meldThis.length) 705 { 706 // Source empty, just return 707 return; 708 } 709 else if (!intoThis.length) 710 { 711 // Source has content but target empty, just inherit 712 intoThis = meldThis.dup; 713 return; 714 } 715 716 // Ensure there's room for all elements 717 if (meldThis.length > intoThis.length) intoThis.length = meldThis.length; 718 } 719 else static if (isStaticArray!Array1 && isStaticArray!Array2) 720 { 721 static if (Array1.length == Array2.length) 722 { 723 if (meldThis == Array1.init) 724 { 725 // Source empty, just return 726 return; 727 } 728 else if (intoThis == Array2.init) 729 { 730 // Source has content but target empty, just inherit 731 intoThis = meldThis; // value type, no need for .dup 732 return; 733 } 734 } 735 else 736 { 737 import std.format : format; 738 static assert((Array2.length >= Array1.length), 739 "Cannot meld a larger `%s` static array into a smaller `%s` static one" 740 .format(Array1.stringof, Array2.stringof)); 741 } 742 } 743 else static if (isDynamicArray!Array1 && isStaticArray!Array2) 744 { 745 assert((meldThis.length <= Array2.length), 746 "Cannot meld a larger dynamic array into a smaller static one"); 747 } 748 else 749 { 750 import std.format : format; 751 static assert(0, "Attempted to meld an unsupported array type: `%s` into `%s`" 752 .format(Array1.stringof, Array2.stringof)); 753 } 754 755 foreach (immutable i, const val; meldThis) 756 { 757 static if (strategy == MeldingStrategy.conservative) 758 { 759 if ((val != typeof(val).init) && (intoThis[i] == typeof(intoThis[i]).init)) 760 { 761 intoThis[i] = val; 762 } 763 } 764 else static if (strategy == MeldingStrategy.aggressive) 765 { 766 if (val != typeof(val).init) 767 { 768 intoThis[i] = val; 769 } 770 } 771 else static if (strategy == MeldingStrategy.overwriting) 772 { 773 intoThis[i] = val; 774 } 775 else 776 { 777 static assert(0, "Logic error; unexpected `MeldingStrategy` passed to array `meldInto`"); 778 } 779 } 780 } 781 782 /// 783 unittest 784 { 785 import std.conv : to; 786 787 auto arr1 = [ 123, 0, 789, 0, 456, 0 ]; 788 auto arr2 = [ 0, 456, 0, 123, 0, 789 ]; 789 arr1.meldInto!(MeldingStrategy.conservative)(arr2); 790 assert((arr2 == [ 123, 456, 789, 123, 456, 789 ]), arr2.to!string); 791 792 auto yarr1 = [ 'Z', char.init, 'Z', char.init, 'Z' ]; 793 auto yarr2 = [ 'A', 'B', 'C', 'D', 'E', 'F' ]; 794 yarr1.meldInto!(MeldingStrategy.aggressive)(yarr2); 795 assert((yarr2 == [ 'Z', 'B', 'Z', 'D', 'Z', 'F' ]), yarr2.to!string); 796 797 auto harr1 = [ char.init, 'X' ]; 798 yarr1.meldInto(harr1); 799 assert((harr1 == [ 'Z', 'X', 'Z', char.init, 'Z' ]), harr1.to!string); 800 801 char[5] harr2 = [ '1', '2', '3', '4', '5' ]; 802 char[] harr3; 803 harr2.meldInto(harr3); 804 assert((harr2 == harr3), harr3.to!string); 805 806 int[3] asdf; 807 int[3] hasdf; 808 asdf.meldInto(hasdf); 809 810 int[] dyn = new int[2]; 811 int[3] stat; 812 dyn.meldInto(stat); 813 } 814 815 816 // meldInto 817 /++ 818 Takes two associative arrays and melds them together, making a union of the two. 819 820 This is largely the same as the array-version [meldInto] but doesn't need 821 the extensive template constraints it employs, so it might as well be kept separate. 822 823 Example: 824 --- 825 int[string] aa1 = [ "abc" : 42, "def" : -1 ]; 826 int[string] aa2 = [ "ghi" : 10, "jkl" : 7 ]; 827 arr1.meldInto(arr2); 828 829 assert("abc" in aa2); 830 assert("def" in aa2); 831 assert("ghi" in aa2); 832 assert("jkl" in aa2); 833 --- 834 835 Params: 836 strategy = To what extent the source object should overwrite set 837 (non-`init`) values in the receiving object. 838 meldThis = Associative array to meld (source). 839 intoThis = Reference to the associative array to meld (target). 840 +/ 841 void meldInto(MeldingStrategy strategy = MeldingStrategy.conservative, QualAA, AA) 842 (QualAA meldThis, ref AA intoThis) pure 843 if (isAssociativeArray!AA && is(QualAA : AA) && isMutable!AA) 844 { 845 if (!meldThis.length) 846 { 847 // Empty source 848 return; 849 } 850 else if (!intoThis.length) 851 { 852 // Empty target, just assign 853 intoThis = meldThis.dup; 854 return; 855 } 856 857 foreach (immutable key, val; meldThis) 858 { 859 static if (strategy == MeldingStrategy.conservative) 860 { 861 if (val == typeof(val).init) 862 { 863 // Source value is .init; do nothing 864 continue; 865 } 866 867 const target = key in intoThis; 868 869 if (!target || (*target == typeof(*target).init)) 870 { 871 // Target value doesn't exist or is .init; meld 872 intoThis[key] = val; 873 } 874 } 875 else static if ((strategy == MeldingStrategy.aggressive) || 876 (strategy == MeldingStrategy.overwriting)) 877 { 878 import std.traits : ValueType; 879 880 static if ((strategy == MeldingStrategy.overwriting) && 881 is(ValueType!AA == bool)) 882 { 883 // Always overwrite 884 intoThis[key] = val; 885 } 886 else 887 { 888 if (val != typeof(val).init) 889 { 890 // Target value doesn't exist; meld 891 intoThis[key] = val; 892 } 893 } 894 } 895 else 896 { 897 static assert(0, "Logic error; unexpected `MeldingStrategy` passed to AA `meldInto`"); 898 } 899 } 900 } 901 902 /// 903 unittest 904 { 905 bool[string] aa1; 906 bool[string] aa2; 907 908 aa1["a"] = true; 909 aa1["b"] = false; 910 aa2["c"] = true; 911 aa2["d"] = false; 912 913 assert("a" in aa1); 914 assert("b" in aa1); 915 assert("c" in aa2); 916 assert("d" in aa2); 917 918 aa1.meldInto!(MeldingStrategy.overwriting)(aa2); 919 920 assert("a" in aa2); 921 assert("b" in aa2); 922 923 string[string] saa1; 924 string[string] saa2; 925 926 saa1["a"] = "a"; 927 saa1["b"] = "b"; 928 saa2["c"] = "c"; 929 saa2["d"] = "d"; 930 931 saa1.meldInto!(MeldingStrategy.conservative)(saa2); 932 assert("a" in saa2); 933 assert("b" in saa2); 934 935 saa1["a"] = "A"; 936 saa1.meldInto!(MeldingStrategy.aggressive)(saa2); 937 assert(saa2["a"] == "A"); 938 }