[swift-evolution] [Pitch] Add the DefaultConstructible protocol to the standard library
Xiaodi Wu
xiaodi.wu at gmail.com
Sun Dec 25 23:44:27 CST 2016
On Mon, Dec 26, 2016 at 12:43 AM, Daniel Leping via swift-evolution <
swift-evolution at swift.org> wrote:
> Thank you, Adam!
>
Wait, are you arguing for implicit conformance or not?
On Mon, 26 Dec 2016 at 11:12 Adam Nemecek via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>> > Swift doesn't do implicit conformance. It always has to be declared
>> explicitly. I'm pretty sure Doug Gregor can explain why better than I
>> could.
>>
>>
>> I don't think Daniel was arguing for implicit conformance, he's saying
>> that if it makes sense for an object to have a default constructor, it
>> makes sense for it to conform to the protocol which I agree with 100%.
>>
>> On Sun, Dec 25, 2016 at 9:17 PM, Dave Abrahams via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>
>>
>>
>> on Sun Dec 25 2016, Daniel Leping <swift-evolution at swift.org> wrote:
>>
>>
>>
>>
>>
>> > You are right, usually it's required to implement a protocol which is
>> not a
>>
>>
>> > good approach. The best is plain objects which can be used
>> independently of
>>
>>
>> > ORM if needed (as DTOs, i.e.).
>>
>>
>> >
>>
>>
>> > I was thinking of DefaultConstructable as a protocol automatically
>> applied
>>
>>
>> > to any class/struct having a default init, which is really logical for
>>
>>
>> > me.
>>
>>
>>
>>
>>
>> Swift doesn't do implicit conformance. It always has to be declared
>>
>>
>> explicitly. I'm pretty sure Doug Gregor can explain why better than I
>>
>>
>> could.
>>
>>
>>
>>
>>
>> > On Mon, 26 Dec 2016 at 9:41 Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>>
>> >
>>
>>
>> >> On Sun, Dec 25, 2016 at 10:50 PM, Daniel Leping
>>
>>
>> >> <daniel at crossroadlabs.xyz>
>>
>>
>> >> wrote:
>>
>>
>> >>
>>
>>
>> >> Ok, an example from ORM. You have an entity factory with a virtual
>> (read,
>>
>>
>> >> overloadable in the subclasses) method populating the properties.
>>
>>
>> >> DefaultConstructable is a great choice here. Otherwise you will have to
>>
>>
>> >> force the users of your ORM to implement a certain protocol, which you
>> most
>>
>>
>> >> probably would like to avoid.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Sorry--I'm not very familiar with using Swift for ORM purposes. Why do
>> you
>>
>>
>> >> want to avoid having your users conform to a certain protocol?
>> Wouldn't the
>>
>>
>> >> users of your ORM have to conform to `DefaultConstructible` then? I'm
>>
>>
>> >> looking at Swift ORMs, and all require users to conform to a protocol
>> or
>>
>>
>> >> inherit from a base class, typically named `Model` or similar. From a
>> quick
>>
>>
>> >> Google search:
>>
>>
>> >>
>>
>>
>> >> https://vapor.github.io/documentation/fluent/model.html
>>
>>
>> >> https://github.com/blitzagency/amigo-swift
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> In general I think the best showcase is generic factories.
>>
>>
>> >>
>>
>>
>> >> On Mon, 26 Dec 2016 at 9:02 Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 10:18 PM, Daniel Leping
>>
>>
>> >> <daniel at crossroadlabs.xyz>
>>
>>
>> >> wrote:
>>
>>
>> >>
>>
>>
>> >> Usually it's a generic function that needs to return a value from some
>>
>>
>> >> other function or a default value (zero) in a case of some conditions.
>>
>>
>> >> Optional value is an arguable solution in quite some scenarios. Afaik,
>>
>>
>> >> sometimes it can be used for optional resolution.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Right, I'd agree that Optional is the idiomatic way to do it. Afaict,
>>
>>
>> >> there's not much you can do with a default value that you couldn't with
>>
>>
>> >> nil, unless you have some guarantee as to _what_ that default is;
>> however,
>>
>>
>> >> I'd expect that in every case that you can rely on a guarantee about a
>>
>>
>> >> default value which would be more useful than nil, it's going to
>> require
>>
>>
>> >> more specific knowledge of your type than an all-encompassing
>>
>>
>> >> `DefaultConstructible` can provide.
>>
>>
>> >>
>>
>>
>> >> Also, generic factories. Widely used in ORM solutions.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Can you elaborate on this? Why is Optional not a solution here?
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> As mentioned above, algorythmical stuff that requires Zero.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> I'm still not convinced there exist credible use cases that need to be
>>
>>
>> >> generic over both collections and floating point, for instance. In
>> fact, in
>>
>>
>> >> my experience, there are few math-heavy algorithms where one can ignore
>>
>>
>> >> even the distinction between integers and binary floating point. By the
>>
>>
>> >> time you get down to matrix math, you start to run into difficulties
>> that
>>
>>
>> >> require separate implementations for Float and Double.
>>
>>
>> >>
>>
>>
>> >> On Mon, 26 Dec 2016 at 8:38 Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>>
>> >>
>>
>>
>> >> Can you give some examples of what you used this approach to do?
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 9:49 PM, Daniel Leping
>>
>>
>> >> <daniel at crossroadlabs.xyz>
>>
>>
>> >> wrote:
>>
>>
>> >>
>>
>>
>> >> +1 to this approach. I remember I had to create it on my own for my
>>
>>
>> >> projects. Would be nice to have it out of the box.
>>
>>
>> >>
>>
>>
>> >> On Mon, 26 Dec 2016 at 8:11 Adam Nemecek via swift-evolution <
>>
>>
>> >> swift-evolution at swift.org> wrote:
>>
>>
>> >>
>>
>>
>> >> > Yes, those particular types have initializers that take no arguments.
>>
>>
>> >> That does not address my question. You merely restated your initial
>>
>>
>> >> observation that many types in Swift have implemented `init()`.
>>
>>
>> >>
>>
>>
>> >> Right, it's an empirical argument.
>>
>>
>> >>
>>
>>
>> >> > I didn't think the value returned by `init()` was regarded as any
>> sort
>>
>>
>> >> of zero--or even any sort of "default." In fact, some types in
>> Foundation
>>
>>
>> >> have a static property called `default` distinct from `init()`.
>>
>>
>> >>
>>
>>
>> >> Let's not talk about those then. This would not apply to every single
>> type
>>
>>
>> >> in existence, as I've stated previously.
>>
>>
>> >>
>>
>>
>> >> > It gives you something different every time. How can this be squared
>>
>>
>> >> with your stated motivation regarding concepts of zero and concepts of
>>
>>
>> >> equality?
>>
>>
>> >>
>>
>>
>> >> Due to the fact that it's a resource, not a value. As I've stated
>> above,
>>
>>
>> >> not all of this applies to types that are more resource-like.
>>
>>
>> >>
>>
>>
>> >> > Or, it's what you get because that's the most trivial possible
>> string.
>>
>>
>> >> Quite simply, I do not think the designer of most types that implement
>>
>>
>> >> `init()` have paused to wonder whether the value that you get is the
>>
>>
>> >> identity element associated with the most useful and prominent
>> operation
>>
>>
>> >> that can be performed on that type. I certainly never have.
>>
>>
>> >>
>>
>>
>> >> This is an appeal to tradition.
>>
>>
>> >>
>>
>>
>> >> > The statement I wrote was in JavaScript, so I'm not sure what you
>> mean
>>
>>
>> >> by returning an optional. `[].reduce((a, b) => a + b)` results in an
>>
>>
>> >> error in JavaScript. In Swift, such a function may also be implemented
>> with
>>
>>
>> >> a precondition that the array is not empty and would not return an
>> optional.
>>
>>
>> >>
>>
>>
>> >> I was talking about their analogous swift implementations.
>>
>>
>> >>
>>
>>
>> >> > Can you give an example of an algorithm dealing with tensors where
>> you
>>
>>
>> >> would use a `DefaultConstructible` generic over all types that have
>>
>>
>> >> `init()`, as opposed to working with the existing `Integer`,
>>
>>
>> >> `FloatingPoint`, and other numerical protocols?
>>
>>
>> >>
>>
>>
>> >> If it's implemented as either nested collections or numbers.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 6:00 PM, Xiaodi Wu <xiaodi.wu at gmail.com>
>> wrote:
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 7:30 PM, Adam Nemecek <adamnemecek at gmail.com>
>>
>>
>> >> wrote:
>>
>>
>> >>
>>
>>
>> >> > Is it well settled, either in Swift or in C++/Rust/etc., that the
>> value
>>
>>
>> >> returned by a default initializer/constructor is regarded as an
>> identity
>>
>>
>> >> element or zero?
>>
>>
>> >>
>>
>>
>> >> Int() == 0, String() == "" so to some extent by convention, a lot of
>> types
>>
>>
>> >> have a default value as is.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Yes, those particular types have initializers that take no arguments.
>> That
>>
>>
>> >> does not address my question. You merely restated your initial
>> observation
>>
>>
>> >> that many types in Swift have implemented `init()`.
>>
>>
>> >>
>>
>>
>> >> I didn't think the value returned by `init()` was regarded as any sort
>> of
>>
>>
>> >> zero--or even any sort of "default." In fact, some types in Foundation
>> have
>>
>>
>> >> a static property called `default` distinct from `init()`. In Rust, the
>>
>>
>> >> Default trait requires a function called `default()`, which is
>> documented
>>
>>
>> >> as being useful when you want "some kind of default value, and don't
>>
>>
>> >> particularly care what it is."
>>
>>
>> >>
>>
>>
>> >> I was asking whether there's some understanding, of which I've been
>>
>>
>> >> unaware, that the result of `init()` (or the equivalent in other
>> languages)
>>
>>
>> >> is expected to be some sort of zero or an identity element. I'm not
>> aware
>>
>>
>> >> of any evidence to that effect. Are you?
>>
>>
>> >>
>>
>>
>> >> > Is the thread that I get by writing `let t = Thread()` some kind of
>> zero
>>
>>
>> >> in any reasonable sense of the word?
>>
>>
>> >>
>>
>>
>> >> DefaultConstructibility makes less sense for types that represent some
>>
>>
>> >> sort of resource but make sense for things that are values. But even in
>>
>>
>> >> this case, Thread() gives you a default value for example if you are
>>
>>
>> >> working with a resizable collection of threads.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> It gives you something different every time. How can this be squared
>> with
>>
>>
>> >> your stated motivation regarding concepts of zero and concepts of
>> equality?
>>
>>
>> >>
>>
>>
>> >> A better question is why does thread currently implement a default
>>
>>
>> >> constructor?
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> It's an initializer that takes no arguments, because none are needed
>> for a
>>
>>
>> >> new thread. How else would you write it?
>>
>>
>> >>
>>
>>
>> >> > Do you mean to argue that for an integer the additive identity
>> should be
>>
>>
>> >> considered "more prominent and useful" than the multiplicative
>> identity?
>>
>>
>> >> I'm not aware of any mathematical justification for such a conclusion.
>>
>>
>> >>
>>
>>
>> >> I do. The justification is that if I call the default constructor of
>> Int
>>
>>
>> >> currently, I get the value of 0.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> This is backwards. Why do you believe that the value you obtain from
>>
>>
>> >> `init()` is intended to be an identity element at all, let alone the
>> most
>>
>>
>> >> important one? (It's also circular reasoning. Since `init()` only ever
>>
>>
>> >> gives you one value at a time, by your reasoning it demonstrates that
>> every
>>
>>
>> >> type must have one "more prominent and useful" identity, which is
>> begging
>>
>>
>> >> the question.)
>>
>>
>> >>
>>
>>
>> >> Which means that the binary operation must be addition.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Based on the value of `Int.init()`, you conclude that addition of
>> integers
>>
>>
>> >> is a "more prominent and useful" operation than multiplication? Again,
>> this
>>
>>
>> >> is backwards. Rather, we know that each numerical type belongs to
>> multiple
>>
>>
>> >> ring algebras; there is no basis for reckoning any as "more useful."
>> Since
>>
>>
>> >> `init()` can only ever give us one value at a time, we know that
>> `init()`
>>
>>
>> >> cannot give a value that is a meaningful default with respect to any
>>
>>
>> >> particular operation.
>>
>>
>> >>
>>
>>
>> >> If I call String() I get "" which is the identity of the + String
>>
>>
>> >> operation.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Or, it's what you get because that's the most trivial possible string.
>>
>>
>> >> Quite simply, I do not think the designer of most types that implement
>>
>>
>> >> `init()` have paused to wonder whether the value that you get is the
>>
>>
>> >> identity element associated with the most useful and prominent
>> operation
>>
>>
>> >> that can be performed on that type. I certainly never have.
>>
>>
>> >>
>>
>>
>> >> > Going to your original example, I should add: other languages
>> provide a
>>
>>
>> >> version of `reduce` that doesn't require an initial result (for
>> instance,
>>
>>
>> >> JavaScript). In JavaScript, `[1, 2, 3].reduce((a, b) => a + b)` uses
>> the
>>
>>
>> >> element at array index 0 as the initial result, and the accumulator
>>
>>
>> >> function is invoked starting with the element at array index 1. This is
>>
>>
>> >> precisely equivalent to having `reduce` use the additive identity as
>> the
>>
>>
>> >> default initial result when + is the accumulator function and the
>>
>>
>> >> multiplicative identity when * is the accumulator function (with the
>>
>>
>> >> accumulator function being invoked starting with the element at array
>> index
>>
>>
>> >> 0). It does not require a DefaultConstructible protocol. What more
>>
>>
>> >> ergonomic solution could be implemented using a monoidic wrapper type?
>>
>>
>> >>
>>
>>
>> >> These two will have different signatures. The reduce you describe
>> returns
>>
>>
>> >> an optional,
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> The statement I wrote was in JavaScript, so I'm not sure what you mean
>> by
>>
>>
>> >> returning an optional. `[].reduce((a, b) => a + b)` results in an error
>>
>>
>> >> in JavaScript. In Swift, such a function may also be implemented with a
>>
>>
>> >> precondition that the array is not empty and would not return an
>> optional.
>>
>>
>> >>
>>
>>
>> >> the other one would returns the default value.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> In what scenario would you prefer to propagate a default after
>> reducing a
>>
>>
>> >> potential empty collection _without supplying an explicit default_ for
>> that
>>
>>
>> >> operation? This would certainly violate the Swift convention of not
>>
>>
>> >> defaulting to zero and, I suspect, most users of Swift would not regard
>>
>>
>> >> that as ergonomic at all.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Fundamentally the default constructibles are useful in numerical
>>
>>
>> >> computations e..g. dealing with tensors.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Can you give an example of an algorithm dealing with tensors where you
>>
>>
>> >> would use a `DefaultConstructible` generic over all types that have
>>
>>
>> >> `init()`, as opposed to working with the existing `Integer`,
>>
>>
>> >> `FloatingPoint`, and other numerical protocols? (I should also add,
>> FWIW, I
>>
>>
>> >> have never seen a generic algorithm written for integers or FP types
>> that
>>
>>
>> >> has preferred the use of `T()` over `0`.)
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 3:30 PM, Xiaodi Wu <xiaodi.wu at gmail.com>
>> wrote:
>>
>>
>> >>
>>
>>
>> >> On Sun, Dec 25, 2016 at 5:27 PM, Adam Nemecek <adamnemecek at gmail.com>
>>
>>
>> >> wrote:
>>
>>
>> >>
>>
>>
>> >> > *Which* APIs become more ergonomic?
>>
>>
>> >>
>>
>>
>> >> I'll get back to this question in a second if I may. This would be a
>>
>>
>> >> longer discussion and I first want to make sure that before we get
>> into the
>>
>>
>> >> details that there is a possibility of this being introduced (I'm
>> asking if
>>
>>
>> >> violating the no zero defaults is more important than slightly more
>>
>>
>> >> ergonomic APIs). But to give a broad answer I think that the concept
>> of a
>>
>>
>> >> zero is closely related to the concept of equality (and all the things
>> that
>>
>>
>> >> build up on equality such as comparability and negation).
>>
>>
>> >>
>>
>>
>> >> > 1) How does this square with Swift’s general philosophy to not
>> default
>>
>>
>> >> initialize values to “zero”?
>>
>>
>> >>
>>
>>
>> >> I actually wasn't aware of this philosophy. Despite this philosophy,
>> look
>>
>>
>> >> at how many types actually currently implement a default constructor.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> (Not a rhetorical question:) Is it well settled, either in Swift or in
>>
>>
>> >> C++/Rust/etc., that the value returned by a default
>> initializer/constructor
>>
>>
>> >> is regarded as an identity element or zero? Is the thread that I get by
>>
>>
>> >> writing `let t = Thread()` some kind of zero in any reasonable sense
>> of the
>>
>>
>> >> word?
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Also can I ask what's the motivation behind this philosophy?
>>
>>
>> >> I think that in Swift, default constructibility makes complete sense
>> for
>>
>>
>> >> (most?) structs, maybe less so for classes.
>>
>>
>> >>
>>
>>
>> >> > 2) To your original example, it isn’t immediately clear to me that
>>
>>
>> >> reduce should choose a default identity. Some types (e.g. integers
>> and FP)
>>
>>
>> >> belong to multiple different ring algebras, and therefore have
>> different
>>
>>
>> >> identity values that correspond to the relevant binary operations.
>>
>>
>> >>
>>
>>
>> >> This is a good point that I've considered as well but felt that for the
>>
>>
>> >> most part, there is one particular identity and associated operation
>> that
>>
>>
>> >> is more prominent and useful than others. Furthermore, modeling
>> different
>>
>>
>> >> algebras isn't mutually exclusive with writing generic algorithms that
>> rely
>>
>>
>> >> on this protocol, you can always introduce some monoidic wrapper type
>> that
>>
>>
>> >> defines the more appropriate default value and operation.
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >> Do you mean to argue that for an integer the additive identity should
>> be
>>
>>
>> >> considered "more prominent and useful" than the multiplicative
>> identity?
>>
>>
>> >> I'm not aware of any mathematical justification for such a conclusion.
>>
>>
>> >>
>>
>>
>> >> Going to your original example, I should add: other languages provide a
>>
>>
>> >> version of `reduce` that doesn't require an initial result (for
>> instance,
>>
>>
>> >> JavaScript). In JavaScript, `[1, 2, 3].reduce((a, b) => a + b)` uses
>> the
>>
>>
>> >> element at array index 0 as the initial result, and the accumulator
>>
>>
>> >> function is invoked starting with the element at array index 1. This is
>>
>>
>> >> precisely equivalent to having `reduce` use the additive identity as
>> the
>>
>>
>> >> default initial result when + is the accumulator function and the
>>
>>
>> >> multiplicative identity when * is the accumulator function (with the
>>
>>
>> >> accumulator function being invoked starting with the element at array
>> index
>>
>>
>> >> 0). It does not require a DefaultConstructible protocol. What more
>>
>>
>> >> ergonomic solution could be implemented using a monoidic wrapper type?
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> >>
>>
>>
>> > _______________________________________________
>>
>>
>> > swift-evolution mailing list
>>
>>
>> > swift-evolution at swift.org
>>
>>
>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> >
>>
>>
>>
>>
>>
>> --
>>
>>
>> -Dave
>>
>>
>>
>>
>>
>> _______________________________________________
>>
>>
>> swift-evolution mailing list
>>
>>
>> swift-evolution at swift.org
>>
>>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>>
>>
>> _______________________________________________
>>
>> swift-evolution mailing list
>>
>> swift-evolution at swift.org
>>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161226/e68b4410/attachment.html>
More information about the swift-evolution
mailing list