<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">To add more real-world project data to this discussion, I just did a test migration of Siesta to Swift 4 using the 2017-05-30 snapshot.</div><div class=""><br class=""></div><div class=""><div class="">Nothing earth-shattering follows — just more practical evidence that the problem needs attention.</div></div><div class=""><br class=""></div><div class="">Here’s what I found:</div><div class=""><br class=""></div><div class=""><b class="">(1)</b> The lack of tuple destructing under discussion here hit the project in half a dozen places. That’s in only a ~2k-line codebase.</div><div class=""><br class=""></div><div class=""><b class="">(2)</b> Every one of those places it hit involved a Dictionary.</div><div class=""><br class=""></div><div class="">Most involved calling map, flatMap, or filter, so adding Dictionary-specific 2-arg flavors of those methods to stdlib would reduce the burden of SE-110. However, some places involved custom Collection extensions (e.g. “any”), so it’s not just the stdlib that would have to add nearly-redundant dictionary-specific variants of Collection methods. Destructuring still has a clear advantage.</div><div class=""><br class=""></div><div class=""><b class="">(3)</b> All the problems were indeed fixable using $0.0 and $0.1 in place of named destructured args, but the damage to readability was … severe. Compare this, for example:</div><div class=""><br class=""></div><div class=""><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp;&nbsp;</span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">let</span><span style="font-variant-ligatures: no-common-ligatures" class=""> nonEmptyStages = stages</span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp; &nbsp; &nbsp; .filter { </span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">_</span><span style="font-variant-ligatures: no-common-ligatures" class="">, stage </span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">in</span><span style="font-variant-ligatures: no-common-ligatures" class=""> </span><span style="font-variant-ligatures: no-common-ligatures; color: #587ea8" class="">!</span><span style="font-variant-ligatures: no-common-ligatures" class="">stage.isEmpty }</span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp; &nbsp; &nbsp; .map { key, </span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">_</span><span style="font-variant-ligatures: no-common-ligatures" class=""> </span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">in</span><span style="font-variant-ligatures: no-common-ligatures" class=""> key }</span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; min-height: 13px;" class=""><span style="font-family: 'Helvetica Neue'; font-size: 13px;" class=""><br class=""></span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; min-height: 13px;" class=""><span style="font-family: 'Helvetica Neue'; font-size: 13px;" class="">…to this:</span><span style="font-variant-ligatures: no-common-ligatures" class=""></span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo; min-height: 13px;" class=""><br class=""></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp; </span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">let</span><span style="font-variant-ligatures: no-common-ligatures" class=""> nonEmptyStages = stages</span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp; &nbsp; &nbsp; .filter { </span><span style="font-variant-ligatures: no-common-ligatures; color: #587ea8" class="">!</span><span style="font-variant-ligatures: no-common-ligatures" class="">$0.</span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">1</span><span style="font-variant-ligatures: no-common-ligatures" class="">.isEmpty }</span></div><div style="margin: 0px; font-size: 11px; line-height: normal; font-family: Menlo;" class=""><span style="font-variant-ligatures: no-common-ligatures" class="">&nbsp; &nbsp; &nbsp; &nbsp; .map { $0.</span><span style="font-variant-ligatures: no-common-ligatures; color: #323e7d" class="">0</span><span style="font-variant-ligatures: no-common-ligatures" class=""> }</span></div></div><div class=""><span style="font-variant-ligatures: no-common-ligatures" class=""><br class=""></span></div><div class=""><span style="font-variant-ligatures: no-common-ligatures" class="">Even though “stage” conveys little information, and “key” even less, the net gain from those named args is&nbsp;</span>large, at least to my eyes.</div><div class=""><br class=""></div><div class=""><b class="">(4)</b> SE-110 is also the thing that prevents Siesta’s dependencies from compiling.</div><div class=""><br class=""></div><div class="">Hope all this helps weigh the tradeoffs of taking this on.</div><div class=""><br class=""></div><div class="">Cheers,</div><div class=""><br class=""></div><div class="">Paul</div><br class=""><div><blockquote type="cite" class=""><div class="">On Jun 1, 2017, at 2:32 PM, Vladimir.S via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div class="">On 01.06.2017 19:31, Tommaso Piazza wrote:<br class=""><blockquote type="cite" class="">Dear all,<br class="">I made a comparison of Swift's 4 lack of tuple unsplatting, here is how it stands in comparison with other languages<br class=""><a href="https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935" class="">https://gist.github.com/blender/53f9568617654c38a219dd4a8353d935</a><br class=""></blockquote><br class="">Thank you! Very useful information. And also I really like the opinion of @AliSoftware in comments for this article.<br class=""><br class="">I'd suggest to add this variant to Swift section in your article:<br class=""><br class="">let eighteenOrMore = ["Tom" : 33, "Rebecca" : 17, "Siri" : 5].filter {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>(arg: (name: String, age: Int)) in arg.age &gt;= 18 }<br class=""><br class="">(I believe it is better that 2 others Swift variants.)<br class=""><br class="">It seems for me that we need to allow some special syntax for *explicit* tuple destructuring in closures to make all happy.<br class=""><br class="">FWIW These suggestions are my favorite:<br class=""><br class="">1. Just allow type inference for tuple's destructured variables in this position:<br class=""><br class="">.filter { (arg: (name, age)) in arg.age &gt;= 18 }<br class=""><br class=""><br class="">2. (1) + allow underscore for tuple argument name:<br class=""><br class="">.filter { (_: (name, age)) in age &gt;= 18 }<br class=""><br class=""><br class="">3. (2) + allow to omit parenthesis (probably only in case of just one tuple argument)<br class=""><br class="">.filter { _: (name, age) in age &gt;= 18 }<br class=""><br class=""><br class="">4. Use pattern matching syntax:<br class=""><br class="">.filter { case let (name, age) in age &gt;= 18 }<br class=""><br class="">(looks similar as allowed today: if case let (name, age) = x { print(name, age) } &nbsp;)<br class=""><br class=""><br class="">5. Use two pairs of parenthesis :<br class=""><br class="">.filter { ((name, age)) in age &gt;= 18 }<br class=""><br class="">Btw, about the 5th variant. If took what is allowed today:<br class="">.filter { (arg: (name: String, age: Int)) in arg.age &gt;= 18 }<br class="">, and allow type inference for tuple part arguments, we'll have this:<br class="">.filter { (arg: (name, age)) in arg.age &gt;= 18 }<br class="">, and if additionally allow skipping of tuple argument declaration we'll have:<br class="">.filter { ((name, age)) in arg.age &gt;= 18 }<br class="">I.e. two pairs for parenthesis for tuple destructuring, and such syntax is similar to the type this closure should have : ((String, Int)) -&gt; Bool<br class=""><br class=""><br class=""><blockquote type="cite" class="">On Thursday, June 1, 2017 12:25 PM, Vladimir.S via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:<br class="">On 01.06.2017 0:42, John McCall wrote:<br class=""> &gt;&gt; On May 31, 2017, at 2:02 PM, Stephen Celis &lt;<a href="mailto:stephen.celis@gmail.com" class="">stephen.celis@gmail.com</a> &lt;<a href="mailto:stephen.celis@gmail.com" class="">mailto:stephen.celis@gmail.com</a>&gt;&gt; wrote:<br class=""> &gt;&gt;&gt; On May 28, 2017, at 7:04 PM, John McCall via swift-evolution<br class=""> &gt;&gt;&gt; &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a> &lt;<a href="mailto:swift-evolution@swift.org" class="">mailto:swift-evolution@swift.org</a>&gt;&gt; wrote:<br class=""> &gt;&gt;&gt;<br class=""> &gt;&gt;&gt; Yes, I agree. &nbsp;We need to add back tuple destructuring in closure parameter<br class=""> &gt;&gt;&gt; lists because this is a serious usability regression. &nbsp;If we're reluctant to<br class=""> &gt;&gt;&gt; just "do the right thing" to handle the ambiguity of (a,b), we should at least<br class=""> &gt;&gt;&gt; allow it via unambiguous syntax like ((a,b)). &nbsp;I do think that we should just<br class=""> &gt;&gt;&gt; "do the right thing", however, with my biggest concern being whether there's<br class=""> &gt;&gt;&gt; any reasonable way to achieve that in 4.0.<br class=""> &gt;&gt;<br class=""> &gt;&gt; Closure parameter lists are unfortunately only half of the equation here. This<br class=""> &gt;&gt; change also regresses the usability of point-free expression.<br class=""> &gt;<br class=""> &gt; The consequences for point-free style were expected and cannot really be<br class=""> &gt; eliminated without substantially weakening SE-0110. &nbsp;Closure convenience seems to<br class=""> &gt; me to be a much more serious regression.<br class="">John, do you also want to say "and without weakening SE-0066"? Because, if I<br class="">understand correctly, in this case:<br class=""> &nbsp;&nbsp;func add(_ x: Int, _ y: Int) -&gt; Int {<br class=""> &nbsp;&nbsp;&nbsp;&nbsp;return x + y<br class=""> &nbsp;&nbsp;}<br class=""> &nbsp;&nbsp;zip([1, 2, 3], [4, 5, 6]).map(add)<br class="">.. we have a clear function type mismatch situation, when map() expects function of<br class="">type ((Int, Int))-&gt;Int, but function of type (Int,Int)-&gt;Int is provided ? So probably<br class="">the additional 'reason' of the 'problem' in this case is SE-0066, no?<br class="">Or I don't understand the SE-0066 correctly..<br class="">Do we want to allow implicit conversions between function type ((Int,Int))-&gt;Int and<br class="">(Int,Int)-&gt;Int?<br class="">Quote from SE-0066:<br class="">---<br class="">(Int, Int) -&gt; Int &nbsp;&nbsp;&nbsp;// function from Int and Int to Int<br class="">((Int, Int)) -&gt; Int &nbsp;// function from tuple (Int, Int) to Int<br class="">---<br class="">During this discussion I see a wish of some group of developers to just return back<br class="">tuple splatting for function/closure arguments, so they can freely send tuple to<br class="">function/closure accepting a list of parameters(and probably vise-versa).<br class="">Is it worth to follow SE-0066 and SE-0110 as is, i.e. disallow tuple deconstructing<br class="">and then, as additive change improve the situation with tuple<br class="">splatting/deconstructing later with separate big proposal?<br class="">Btw, about the SE-0110 proposal. It was discussed, formally reviewed and accepted. I<br class="">expect that its revision also should be formally proposed/reviewed/accepted to<br class="">collect a wide range of opinions and thoughts, and attract the attention of<br class="">developers in this list to the subject.<br class="">Also, if we revisit SE-0110, will this code be allowed?:<br class="">func foo(_ callback: ((Int,Int))-&gt;Void) {}<br class="">let mycallback = {(x:Int, y:Int)-&gt;Void in }<br class="">foo(mycallback)<br class="">and<br class="">func foo(_ callback: (Int,Int)-&gt;Void) {}<br class="">let mycallback = {(x: (Int, Int))-&gt;Void in }<br class="">foo(mycallback)<br class="">If so, what will be result of this for both cases? :<br class="">print(type(of:mycallback)) // (Int,Int)-&gt;Void or ((Int,Int))-&gt;Void<br class="">If allowed, do we want to allow implicit conversion between types (Int,Int)-&gt;Void and<br class="">((Int,Int))-&gt;Void in both directions? &nbsp;(Hello tuple splatting?)<br class=""> &gt;<br class=""> &gt; John.<br class=""> &gt;<br class=""> &gt;<br class=""> &gt;&gt;<br class=""> &gt;&gt; func add(_ x: Int, _ y: Int) -&gt; Int { return x + y }<br class=""> &gt;&gt;<br class=""> &gt;&gt; zip([1, 2, 3], [4, 5, 6]).map(add)<br class=""> &gt;&gt;<br class=""> &gt;&gt; // error: nested tuple parameter '(Int, Int)' of function '(((_.Element,<br class=""> &gt;&gt; _.Element)) throws -&gt; _) throws -&gt; [_]' does not support destructuring<br class=""> &gt;&gt;<br class=""> &gt;&gt; This may not be a common pattern in most projects, but we heavily use this style<br class=""> &gt;&gt; in the Kickstarter app in our functional and FRP code. Definitely not the most<br class=""> &gt;&gt; common coding pattern, but a very expressive one that we rely on.<br class=""> &gt;&gt;<br class=""> &gt;&gt; Our interim solution is a bunch of overloaded helpers, e.g.:<br class=""> &gt;&gt;<br class=""> &gt;&gt; func tupleUp&lt;A, B, C&gt;(_ f: (A, B) -&gt; C) -&gt; ((A, B)) -&gt; C { return }<br class=""> &gt;&gt;<br class=""> &gt;&gt; zip([1, 2, 3], [4, 5, 6]).map(tupleUp(add))<br class=""> &gt;&gt;<br class=""> &gt;&gt; Stephen<br class=""> &gt;<br class=""> &gt; .<br class=""> &gt;<br class="">_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a> &lt;<a href="mailto:swift-evolution@swift.org" class="">mailto:swift-evolution@swift.org</a>&gt;<br class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br class=""></blockquote>_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></div></blockquote></div><br class=""></body></html>