[swift-evolution] [Pitch] Add the DefaultConstructible protocol to the standard library

Xiaodi Wu xiaodi.wu at gmail.com
Sun Dec 25 23:08:36 CST 2016


On Sun, Dec 25, 2016 at 11:46 PM, Daniel Leping <daniel at crossroadlabs.xyz>
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.
>

Again, protocols aren't about just syntax but about semantics. One
implication is that it's perfectly logical to have protocols with no syntax
requirements at all, i.e. `protocol MyProtocolWithSpecialSemantics { }`.
Another implication, therefore, is that Swift does not automatically
conform types to a protocol simply because it implements all requirements,
because there's no way for the compiler to judge semantics. If a protocol
can be automatically applied, it's a pretty good sign that it has no
semantic requirements and shouldn't be a protocol.


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?
>>
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161226/527b284d/attachment.html>


More information about the swift-evolution mailing list