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