<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="">One possibility which might be easier would be to just depreciate Float’s Equatable conformance and provide a warning/fixit (suggesting how to use the new ‘~’ relation).<div class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Oct 25, 2017, at 2:35 AM, Jonathan Hull <<a href="mailto:jhull@gbis.com" class="">jhull@gbis.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">There is something interesting here. I like that ‘Bool?’ really represents what we need from the relation on Float. I think it solves the objection that Xiaodi mentioned with a PartialEq protocol. If everyone just switches to using PartialEq in generic contexts because they want it to work with Floats (which is the outcome he was worried about)… then they will be forced to deal with the optional! This brings Swift’s strengths into play, and we will have gained correctness.<div class=""><br class=""></div><div class="">The issue is migration though. Let’s say we name the partial equivalence relation ‘~’ and then we remove Float’s Equivalence conformance (no more == for Float). To migrate, we would need to replace any ‘a == b’ where there is a Float with ‘(a ~ b) == true’. Not pretty, but not impossible.</div><div class=""><br class=""></div><div class="">The trouble spot will be where someone has written their own method which is generic on Equatable, and they are passing Floats to it. We need to be careful about the warning given, so that it sends them in the right direction. The good news is that if they do rewrite the function to use ‘~' it should actually cause them to fix bugs which were there (or at least consider the case of NaN) when dealing with Floats. So, overall good things, but a bit annoying while transitioning.</div><div class=""><br class=""></div><div class="">We should keep thinking/discussing. I am convinced there is an interesting thread to pull on here...<br class=""><div class=""><br class=""></div><div class=""><br class=""><div class=""><blockquote type="cite" class=""><div class="">On Oct 24, 2017, at 11:25 PM, David Sweeris via swift-dev <<a href="mailto:swift-dev@swift.org" class="">swift-dev@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><blockquote type="cite" class=""><div class=""><br class="Apple-interchange-newline">On Oct 24, 2017, at 9:06 PM, Xiaodi Wu via swift-dev <<a href="mailto:swift-dev@swift.org" class="">swift-dev@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;">On Tue, Oct 24, 2017 at 10:08 PM, Ben Cohen<span class="Apple-converted-space"> </span><span dir="ltr" class=""><<a href="mailto:ben_cohen@apple.com" target="_blank" class="">ben_cohen@apple.com</a>></span><span class="Apple-converted-space"> </span>wrote:<br class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><span class=""><div class=""></div><div class=""><br class=""></div><div class=""><br class="">On Oct 24, 2017, at 6:48 PM, Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com" target="_blank" class="">xiaodi.wu@gmail.com</a>> wrote:<br class=""><br class=""></div><blockquote type="cite" class=""><div class=""><div dir="ltr" class="">On Tue, Oct 24, 2017 at 1:55 PM, Ben Cohen<span class="Apple-converted-space"> </span><span dir="ltr" class=""><<a href="mailto:ben_cohen@apple.com" target="_blank" class="">ben_cohen@apple.com</a>></span><span class="Apple-converted-space"> </span>wrote:<br class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><br class=""><div class=""><span class="m_-3438177037803381301gmail-"><br class=""><blockquote type="cite" class=""><div class="">On Oct 19, 2017, at 4:29 PM, Xiaodi Wu via swift-dev <<a href="mailto:swift-dev@swift.org" target="_blank" class="">swift-dev@swift.org</a>> wrote:</div><div class=""><div class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;"><br class=""></div></div></blockquote><blockquote type="cite" class=""><div class=""><div class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">Differing behavior in generic and concrete contexts is simply too subtle to be understandable to the reader.</div></div></blockquote><div class=""><br class=""></div></span><div class="">Hardly more subtle then the current “Equatable works like this, with these strong guarantees. Oh, except for some cases it doesn’t, in which case ¯\_(ツ)_/¯”</div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I'm not saying that the status quo is a superior alternative.</div><div class=""><br class=""></div><div class="">However, one option is to _weaken_ the guarantees of Equatable such that it guarantees only partial equivalence for `==`. From the perspective of documented semantics, it's not subtle at all but a giant hammer of a change. However, from an actual what-does-the-implementation-<wbr class="">do standpoint, it would be acknowledging what is already true. Only code that is already broken when used with floating-point values would become formally "incorrect" in the sense of relying on semantics that are then no longer guaranteed.</div><div class=""><br class=""></div><div class="">Such a solution would avoid, as you might say, perpetuating the ¯\_(ツ)_/¯ approach to floating point.</div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><div class=""><div class="">I realize that Comparable admits an exception for FP. This is, IMO, a serious mistake and needs to be reversed. Equatable has no such exception and rightly so.</div><div class=""><br class=""></div><div class="">The clearest demonstrations of how flawed this approach is can be found in the Standard Library. You can throw a brick at it and hit an example of something that’s broken by the presence of .nan: random sort orders, == implementations that differ based on the identify of the buffer,</div></div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">In my view, if a sort algorithm cannot accommodate NaN, it's entirely acceptable to trap on NaN--and that is a trivial change.</div></div></div></div></div></blockquote><div class=""><br class=""></div></span><div class="">I think this would be user-hostile. This isn’t like out-of-bounds subscript where it’s just not possible to reasonably proceed. NaNs crop up and people don’t expect them to trap when you sort – they expect them to sort to one end, like in Excel.</div></div></blockquote><div class=""><br class=""></div><div class="">Honestly, I don't know that most users have thought about this possibility at all. Sure, a sort that matches IEEE total order _might_ be justifiable. But users are as likely to expect that the last item in the sorted collection will be the greatest and that the first item in the sorted collection will be the smallest. Now, you can say that NaN compares larger than everything, everywhere. But the moment that they try to plug that last element into, say, an AppKit UI function, they're toast.</div><div class=""><br class=""></div><div class="">I certainly disagree with ideas of trapping on NaN inside `==` or similar functions, but I really do think that an argument can be made that it is not reasonable to proceed with sorting an array that contains NaN.</div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><span class=""><br class=""><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class="">After all, NaN is unordered with respect to everything and one cannot sort the unsortable. And, as shown, the `Array.==` implementation is trivially fixable. The entire standard library can be made NaN-safe in like manner.</div></div></div></div></div></blockquote><blockquote type="cite" class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><div class=""> </div></div></div></div></blockquote><div class=""><br class=""></div></span><div class="">My point was, it’s not about what we can do in the standard library. The std lib only has a handful of methods and sure, we can fix them one by one. It’s about whether the standard library defines types and protocols such that it’s reasonable for programmers to use them to write<span class="Apple-converted-space"> </span><span class="" style="background-color: rgba(255, 255, 255, 0);">and use </span>generic algorithms correctly. I’m citing the existing std lib implementations as proof that it’s easy to make mistakes. And I think a more complicated approach, with more operators, more properties, more rules, won’t fix this problem.</div></div></blockquote><div class=""><br class=""></div><div class="">Well, to my mind, this problem you state really works out to:</div><div class=""><br class=""></div><div class="">(a) People expect generic algorithms that operate on Comparable types to work correctly with floating-point types</div><div class="">(b) Generic algorithms that operate on Comparable types don't work correctly with floating-point types unless the author is very, very careful</div><div class="">(c) People shouldn't have to be very, very careful to write working generic algorithms that work with floating-point types</div><div class=""><br class=""></div><div class="">Which, in turn, really boils down to:</div><div class=""><br class=""></div><div class="">(d) People expect floating-point types not to have numerous unintuitive (but learnable) properties, including NaN being unordered</div><div class="">(e) Floating-point types have numerous unintuitive (but learnable) properties, including NaN being unordered</div><div class=""><br class=""></div><div class="">The reason I'm writing to swift-dev (rather than evolution) is that my interest is in fixing the standard library. I'm not even convinced that this problem you state is fixable, at least on your terms. In the interest of not increasing the API surface area, you would propose to blow away (e) in the generic but not concrete context. Now, while it's true that an alternative to increasing the API surface area is to have the same API exhibit context-specific behaviors, that certainly isn't any less complicated conceptually, as we would then be faced with the notion that floating-point types both have and do not have numerous unintuitive properties, depending on the context in which they are used.</div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><div class=""><div class="h5"><blockquote type="cite" class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><div class=""><div class="">arbitrary duplication in Set/Dictionary etc. </div></div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">(I disagree that it's arbitrary. If NaN != NaN, then every NaN is properly unique.)</div><div class=""> </div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><div class=""><div class=""></div><div class="">The important point to take from this is not “how do we fix the Standard Library?” but rather “these errors are easy to make” by anyone writing generic code using standard protocols. If the Standard Library can’t get these right, how can we expect others to? There are potentially far worse bugs that could result. A differently-written sorting algorithm could corrupt elements (because it relied on substitutability). Other sorting or searching algorithms could easily go into an infinite loop. These problems exist because the code relies on the documented behavior of the protocol, because if you can’t, then what is the point in documenting that behavior?</div></div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">It's not that the standard library *can't* get these right, but that it currently *doesn't*, because it documents one set of semantics but implements another, then relies on documented semantics that it knows it does not implement. We both agree that this needs to be fixed.</div><div class=""><br class=""></div><div class="">The question here is whether it is to be fixed by sticking to the documented semantic guarantees of `==` and bringing all implementations into proper conformance, or alternatively sticking to the implemented behavior of `==` and aligning the documented semantic guarantees to that.</div><div class=""> <br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><div class=""><div class="">I don’t support solutions such as adding a property indicating “containsExceptionalValues” (whatever that means), and expecting every author of a generic algorithm that uses Equatable to remember to call it, and craft custom paranoid behavior (if there is any reasonable behavior) based on it. With recursive conformance landed on master, we finally have a generics system where writing algorithms against Collection can be considered approachable by ordinary users. You no longer have to know things like how Collection.SubSequence needs to be constrained to also be a Collection – it just is. We would be negating this good work to now introduce a whole new set of gotchas that we expect people to know (without the type system even helping them in this case) about how some types, including standard library types, flout the documented rules for Equatable and Comparable, and that you need to use one of a handful of properties to hack in special cases to handle it.</div></div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">The gotchas aren't new; they arise when using floating point values, originate with the IEEE definition of floating point equivalence, and exist in some form in every language that has implemented collections of floating point values. Crucially, they exist today in Swift; only, we haven't documented it.</div><div class=""><br class=""></div></div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><div class=""><div class="">And as a user of algorithms, what should you do? If a generic algorithm doesn’t document how it handles these special cases, should you assume it doesn’t? Check the code? Experiment to find out?</div><div class=""><br class=""></div><div class="">This problem also spreads, virus-like, once we have conditional conformance that makes containers equatable when their elements are. <span class="" style="font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: rgb(36, 41, 46); font-size: 11.899999618530273px;">[Double]</span> would need to propagate it’s elements’ “exceptionality", to avoid problems with <span class="" style="font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: rgb(36, 41, 46); font-size: 11.899999618530273px;">[Double]</span>. <span class="" style="font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: rgb(36, 41, 46); font-size: 11.899999618530273px;">Double? </span>will have to do the same.</div></div></div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This isn't a _problem_. In fact, I consider this to be a key _feature_. Naturally, every protocol conformance (conditional or not) must implement all protocol requirements, so if we add additional requirements they must be implemented. What I'm saying here is that *it may be desirable* to have some protocol-based API to distinguish partial from full equivalence relations. If you accept that premise, then it is the logical consequence that if you conditionally conform `Array` to `Equatable`, you will have to implement any new APIs, and in so doing, document how equivalence of arrays of floating point values relates to floating point equivalence. For me, this is a _good thing_: it documents _in code_ something that today is muddled through.</div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class=""><span class="m_-3438177037803381301gmail-"><blockquote type="cite" class=""><div class=""><div class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px;">The explanation that a method on `Float` is a "floating-point context" but a method on `[Float]` is *not a "floating point context"* is, IMO, indefensible.</div></div></blockquote></span></div><br class=""><div class="">Nevertheless, I will attempt to defend it :) </div><div class=""><br class=""></div><div class="">I find it odd that violating the documented requirements of a protocol is considered defensible, but expecting types comply with those requirements is indefensible. A principled stance would be to say that Float shouldn’t conform to Equatable (because… it doesn’t!) and requiring all calls to supply a predicate (and maybe amending types like Dictionary to allow you to supply one). That won’t fly though – users would complain – so instead we are in this murky ground.</div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I don't think we should defend violating the documented requirements of a protocol. Either (a) Float should not conform to Equatable (agree, this is a non-starter); (b) how Float conforms to Equatable should be brought into conformance with documented semantics (your stance); or (c) what semantics are documented should be brought into alignment with how conformance is actually implemented (my stance). Naturally, in the last case, additional APIs should be added as needed to make such reduced semantic guarantees useful for generic algorithms.</div><div class=""><br class=""></div></div></div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="">Later in the thread, you mention a possible fix for <span class="" style="color: rgb(36, 41, 46); font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 11.899999618530273px;">sort</span>:</div><span class="m_-3438177037803381301gmail-"><div class=""><br class=""></div><div class=""><blockquote type="cite" class=""><span class="" style="display: inline;">`sort()` is problematic, but not if a custom predicate is supplied.</span></blockquote></div><div class=""><span class="" style="display: inline;"><br class=""></span></div></span><div class=""><span class="" style="display: inline;">So, </span>we are potentially trading off one subtlety (that < behaves differently in generic and non-generic contexts) for another (that you need to know that you need to pass in a special predicate for sorting, or you get nonsense results). Knowing when an algorithm requires you to supply a predicate (like sort) vs when handling for the special case is built in (like equatable) seems far worse complication to me than knowing one rule: that generically when constrained to Comparable, Float adheres to the requirements of Comparable. Always. That is a consistent rule that you need to learn once and that doesn’t vary depending on which algorithm you’re using.</div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">I would argue that Float should _always_ adhere to the requirements of Comparable, in all contexts. The question is, rather: what can be the requirements of Comparable such that Float can always adhere to them?</div><div class=""><br class=""></div></div></div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div dir="ltr" class=""><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div dir="auto" class="" style="word-wrap: break-word;"><div class="">Another alternative proposed in previous threads is to give Comparable an additional operator (<=> or .compare(to:) that will always enforce a total ordering, and sort can use that. This is, afaict, C#’s solution – double.NaN < 1.0, 1.0 < double.NaN and double.NaN == double.NaN all return false, but Comparer<double>.Default.c<wbr class="">ompare returns -1, 1 and 0 respectively.</div></div></div></div></div></div></div></div></div></div></blockquote><div class=""><br class=""></div><div class="">This is, essentially, the endpoint of what I'm proposing.</div><div class=""><br class=""></div><div class="">Equatable would vend (modulo bikeshedding):</div><div class="">`==`, a partial equivalence relation</div><div class="">`~`, a full equivalence relation<br class=""></div><div class="">`containsExceptionalValues` (yes, this is a deliberately terrible name, because it's meant to go through bikeshedding), a Boolean value to indicate whether `==` is the same as `~`</div><div class=""><br class=""></div><div class="">Comparable would vend (modulo bikeshedding):</div><div class="">`<`, `>`, <=`, `>=`, defined as now<br class=""></div><div class=""><div class="">`<=>`, as in C# `compare` (or maybe, to emphasize the point, `<~>`)</div><div class="">`containsExceptionalValues`, inherited from `Equatable`, to document the relationship between `<` (etc.) and the spaceship operator</div></div><div class=""> </div></div></div></div></div></blockquote><div class=""><br class=""></div></div></div><div class="">This looks to me to be an absurd mess of operations, none of which will have much hope of being used in a coherent fashion by most people. Should I use == or ~ here? What are the rules again? Will people remember to not use < when they really need <=>? Probably not. Did the author of this framework I’m using remember? Dunno.</div></div></blockquote><div class=""><br class=""></div><div class="">The syntax here is not the point (or if it is, it can be bikeshedded). The point I'm trying to make is that what you're criticizing as _incoherent_ is also _inescapable_. Floating-point types have a notion of equivalence that isn't full equivalence. For certain use cases (both concrete and generic), we want that partial equivalence, while for other use cases (both concrete and generic), we truly want full equivalence. To work with floating-point types correctly, a user must know that there is a difference between the two. If there is no hope of "most people" understanding this distinction when one relation is named `==` and the other is named `~`, then _a fortiori_ there is no hope of "most people" understanding the distinction when they're conflated into one operator `==` that has different behaviors in different contexts.</div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204); padding-left: 1ex;"><div dir="auto" class=""><div class="">The C# model of compare works because < is not available generically. There is no choice between < and <=>, and so the model is simple and easily understood by both algorithm implementors and users. And if you need a different ordering, you can supply your own custom comparator. As far as I can tell, it’s a good model and users are happy with it.<span class="Apple-converted-space"> </span><span class="" style="background-color: rgba(255, 255, 255, 0);">Swift is different, since the concrete <<span class="Apple-converted-space"> </span><i class="">is</i>exposed to the generic implementation, but having two possibilities and expecting users to pick is IMO a bad idea. Hence the proposed fix that Float’s Comparable.< is required to be a total order, per the requirements of Comparable, essentially giving us the C# model.</span></div></div></blockquote><div class=""><br class=""></div><div class="">A true C# model would be fine, but the key point of that model to my mind is that partial equivalence and full equivalence are spelled differently (that is, `==` and `Equals`, respectively). It would not work with IEEE `==` being spelled the same way as Comparable `==`. If we were to rename the IEEE operation `&==` instead, then we'd functionally have a design that's broadly similar to the earlier version, only with different names:</div><div class=""><br class=""></div><div class="">Equatable would vend `==`, a full equivalence relation (and `!=`)</div><div class="">Comparable would vend `<`, `>`, `<=`, `>=`, now operators that reflect a total order over the set of all values; and maybe `<=>`</div><div class="">Floating point would additionally vend `&==` and `&<` (and `&!=`, `&<`, `&>`, `&<=`, `&>=`)</div><div class=""><br class=""></div><div class="">One key difference here would be that the partial equivalence relation would now only be found on floating-point types, and it would not be possible to write a generic algorithm that operates on any partially equatable or equatable type. But the other--and major--issues would be (a) that all concrete uses of floating-point comparison operators would have to be migrated to append an extra `&`; and (b) this syntax suggests that most users want to use `==` *instead of* `&==`, which I'm not sure is the case--and certainly isn't the case if they're trying to do the same things they're used to doing with floating-point values in other languages.</div></div></div></div></div></blockquote><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">What about having the protocol hierarchy look like this? (All names subject to bikeshedding, of course)</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="" style="margin: 0px; font-size: 18px; line-height: normal; font-family: 'Fantasque Sans Mono'; background-color: rgb(0, 57, 70);"><div class="" style="color: rgb(222, 81, 148); margin: 0px; line-height: normal;"><div class="" style="margin: 0px; line-height: normal;"><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="" style="color: rgb(222, 81, 148);">protocol</span><span class="Apple-converted-space"> </span>MaybeEquatable {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?== (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>?</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="" style="color: rgb(222, 81, 148);">protocol</span> MostlyEquatable :<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">MaybeEquatable</span><span class="Apple-converted-space"> </span>{</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>== (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(127, 135, 207);"><span class="" style="color: rgb(222, 81, 148);">extension </span><span class="" style="color: rgb(164, 176, 177);">MostlyEquatable</span><span class="" style="color: rgb(164, 176, 177);"> {</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?== (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>? {<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">return</span><span class="Apple-converted-space"> </span>lhs == rhs } <span class="" style="color: rgb(106, 129, 136);">// allows a `MostlyEquatable` or `Equatable` to function as a `MaybeEquatable` without any extra code</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><span class="" style="color: rgb(222, 81, 148);">protocol</span><span class="" style="color: rgb(164, 176, 177);"> Equatable : </span><span class="" style="color: rgb(164, 176, 177);">MostlyEquatable</span><span class="" style="color: rgb(164, 176, 177);"> {}<span class="Apple-converted-space"> </span></span>// purely a semantic difference, no extra syntax</div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><br class=""></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="" style="color: rgb(222, 81, 148);">protocol</span><span class="Apple-converted-space"> </span>MaybeComparable : <span class="" style="color: rgb(127, 135, 207);">MaybeEquatable</span> {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?< (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>?</div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><span class="" style="color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span></span>// plus the rest of them</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(127, 135, 207);"><span class="" style="color: rgb(222, 81, 148);">protocol</span><span class="" style="color: rgb(164, 176, 177);"> </span><span class="" style="color: rgb(164, 176, 177);">MostlyComparable</span><span class="" style="color: rgb(164, 176, 177);"> :<span class="Apple-converted-space"> </span></span>MaybeComparable<span class="" style="color: rgb(164, 176, 177);">,<span class="Apple-converted-space"> </span></span>MostlyEquatable<span class="" style="color: rgb(164, 176, 177);"><span class="Apple-converted-space"> </span>{</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>< (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><span class="" style="color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span></span>// plus the rest of them</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(127, 135, 207);"><span class="" style="color: rgb(222, 81, 148);">extension</span><span class="" style="color: rgb(164, 176, 177);"> </span><span class="" style="color: rgb(164, 176, 177);">MostlyComparable</span><span class="" style="color: rgb(164, 176, 177);"> {</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?< (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Self</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>? {<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">return</span><span class="Apple-converted-space"> </span>lhs < rhs } <span class="" style="color: rgb(106, 129, 136);">// allows a `MostlyComparable` or `Comparable` to function as a `MaybeComparable` without any extra code</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><span class="" style="color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span></span>// plus the rest of them</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><span class="" style="color: rgb(222, 81, 148);">protocol</span><span class="" style="color: rgb(164, 176, 177);"> Comparable : </span><span class="" style="color: rgb(127, 135, 207);">MostlyComparable</span><span class="" style="color: rgb(164, 176, 177);">,<span class="Apple-converted-space"> </span></span><span class="" style="color: rgb(127, 135, 207);">Equatable</span><span class="" style="color: rgb(164, 176, 177);"><span class="Apple-converted-space"> </span>{}<span class="Apple-converted-space"> </span></span>// purely a semantic difference, no extra syntax</div><div class="" style="margin: 0px; line-height: normal; color: rgb(106, 129, 136);"><br class=""></div><div class="" style="margin: 0px; line-height: normal; color: rgb(127, 135, 207);"><span class="" style="color: rgb(222, 81, 148);">extension</span><span class="" style="color: rgb(164, 176, 177);"><span class="Apple-converted-space"> </span></span>Double<span class="" style="color: rgb(164, 176, 177);"><span class="Apple-converted-space"> </span>: </span>MostlyComparable<span class="" style="color: rgb(164, 176, 177);"> {</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?== (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Double</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Double</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>? {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">                </span><span class="" style="color: rgb(222, 81, 148);">return</span><span class="Apple-converted-space"> </span>lhs.<span class="" style="color: rgb(47, 175, 169);">isNaN</span><span class="Apple-converted-space"> </span>|| rhs.<span class="" style="color: rgb(47, 175, 169);">isNaN</span><span class="Apple-converted-space"> </span>?<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">nil</span><span class="Apple-converted-space"> </span>: lhs == rhs</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span><span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">func</span><span class="Apple-converted-space"> </span>?< (lhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Double</span>, rhs:<span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Double</span>) -><span class="Apple-converted-space"> </span><span class="" style="color: rgb(127, 135, 207);">Bool</span>? {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">                </span><span class="" style="color: rgb(222, 81, 148);">return</span><span class="Apple-converted-space"> </span>lhs.<span class="" style="color: rgb(47, 175, 169);">isNaN</span><span class="Apple-converted-space"> </span>|| rhs.<span class="" style="color: rgb(47, 175, 169);">isNaN</span><span class="Apple-converted-space"> </span>|| (lhs.<span class="" style="color: rgb(47, 175, 169);">isInfinite</span><span class="Apple-converted-space"> </span>==<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">true</span><span class="Apple-converted-space"> </span>&& rhs.<span class="" style="color: rgb(47, 175, 169);">isInfinite</span><span class="Apple-converted-space"> </span>==<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">true</span><span class="Apple-converted-space"> </span>&& lhs.<span class="" style="color: rgb(47, 175, 169);">sign</span><span class="Apple-converted-space"> </span>== rhs.<span class="" style="color: rgb(47, 175, 169);">sign</span>) ?<span class="Apple-converted-space"> </span><span class="" style="color: rgb(222, 81, 148);">nil</span><span class="Apple-converted-space"> </span>: lhs < rhs</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span> <span class="" style="color: rgb(222, 81, 148);">func</span> == (lhs: <span class="" style="color: rgb(127, 135, 207);">Double</span>, rhs: <span class="" style="color: rgb(127, 135, 207);">Double</span>) -> <span class="" style="color: rgb(127, 135, 207);">Bool</span> {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">                </span><span class="" style="color: rgb(106, 129, 136);">// whatever current impl is</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span><span class="" style="color: rgb(222, 81, 148);">static</span> <span class="" style="color: rgb(222, 81, 148);">func</span> < (lhs: <span class="" style="color: rgb(127, 135, 207);">Double</span>, rhs: <span class="" style="color: rgb(127, 135, 207);">Double</span>) -> <span class="" style="color: rgb(127, 135, 207);">Bool</span> {</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">                </span><span class="" style="color: rgb(106, 129, 136);">// whatever current impl is</span></div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);">}</div><div class="" style="margin: 0px; line-height: normal; color: rgb(164, 176, 177);"><br class=""></div></div></div></div></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="">This would let people easily switch between the two kinds of "correct" generic comparisons (returning a `Bool?` for types that could have invalid comparisons and a `Bool` for types that can't), as well as easily opting into using IEEE-754's current "arguably incompatible with sane generic programming" behavior (by constraining T to "MostlyComparable" instead of "Comparable") if that's what they really want.</div><div class=""><br class=""></div><div class="">`Collection` could have different implementations of `==` depending on whether `Element` conforms to "MaybeEquatable" or "MostlyEquatable/Equatable", solving the "a = [Double.nan]; print(a == a) // prints true" issue.</div><div class=""><br class=""></div><div class="">This doesn't involve trapping on anything, so dunno what the performance implications would be. All the extra work would be contained in the "?*" functions, though, so at least existing code wouldn't sudden get slower.</div><div class=""><br class=""></div><div class="">- Dave Sweeris</div></div><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">_______________________________________________</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">swift-dev mailing list</span><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><a href="mailto:swift-dev@swift.org" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;" class="">swift-dev@swift.org</a><br style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><a href="https://lists.swift.org/mailman/listinfo/swift-dev" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px;" class="">https://lists.swift.org/mailman/listinfo/swift-dev</a></div></blockquote></div><br class=""></div></div></div></div></blockquote></div><br class=""></div></body></html>