[Proposal] Property behaviors

Few questions:

1 - This model has any optimization opportunities in mind? Like allow the
compiler optimize calls over behaviors?

2 - Will be possible compose behaviors and resuse, like:
typealias lazyObserver = [lazy,observer]

3 - "var behavior" will make unusual create any other property called
behavior. Its not better make behavior a reserved word like "protocol" and
use then? protocol is also a valid identifier.

4 - Will be provided any implementation of standard library of any other
behavior like "lazy", "observer"?

5 - How to handle with behavior name collision? If two modules define the
same behavior name, can be handled using full name (module.behavior)?

···

Em sáb, 16 de jan de 2016 às 17:27, Joe Groff via swift-evolution < swift-evolution@swift.org> escreveu:

On Jan 15, 2016, at 5:54 PM, Matthew Johnson <matthew@anandabits.com> > wrote:

On Jan 15, 2016, at 7:45 PM, Joe Groff via swift-evolution < > swift-evolution@swift.org> wrote:

On Jan 15, 2016, at 6:42 AM, plx via swift-evolution < > swift-evolution@swift.org> wrote:

One more “how will this work?” question: optionals.

Specifically, consider something like this:

// annoyingly-long protocol:
protocol ControlExchanging {
  typealias Context
  func willTakeControlFrom(other: Self?, context: Context)
  func didTakeControlFrom(other: Self?, context: Context)

  func takeControl(context: Context)
  func cedeControl(context: Context)

  func willCedeControlTo(other: Self?, context: Context)
  func didCedeControlTo(other: Self?, context: Context)
}

var behavior exchangeState<Value:ControlExchanging where
Self:Value.Context> : Value {
  var value: Value
  // here:
  set {
    let oldValue = value
    // boilerplate-choreography begins:
    newValue.willTakeControlFrom(oldValue, context: self)
    oldValue.willCedeControlTo(newValue, context: self)
    oldValue.cedeControl(self)
    value = newValue
    newValue.takeControl(self)
    oldValue.didCedeControlTo(newValue, context: self)
    newValue.didTakeControlFrom(oldValue, context: self)
  }
}

// numerous extraneous details omitted:
class GenericSwitchboard<Delegate:ControlExchanging were Delegate.Context
== Self> {

  private(set) weak var [exchangeControl] delegate: Delegate? = nil

}

…which presumably won’t actually work unless I’ve either:

- added an additional implementation that’s typed-as `Value?`
- added a conditional-conformance for `ControlExchanging` to `Optional`
(easy, but boilerplate)

….both of which are workable, neither of which feels optimal (and for the
latter, consider also that in many cases such conformances may generally be
undesirable).

Is there a trick/detail I’m missing here?

No, I think you've got it. This seems like a general problem to me,
though; it'd be nice if protocol conformances could be easily forwarded,
for instance from Optional<T> to T.

Any thoughts about how that might work when requirements have a return
value? Or are you just referring to forwarding conformances when the
protocol doesn’t have members with return values?

I don't have a great answer in mind, unfortunately. You're right that it
only really makes sense for "sink"-like protocols, where none of the
requirements produce a value, or theoretically for protocols where all
results are of associated types that could be optionalized. The sink
protocol use case comes up all the time, though, especially with delegates
and callbacks, and is one the places where ObjC's nil-messaging behavior
feels legitimate.

-Joe

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

One thought is that it might be good to allow behaviors to have `init` accessor that is used if the property is assigned by an initializer of the containing type (only the first time the property is assigned). This would clarify what happens during initialization of the containing type and allow for different init and set code paths when necessary. It would be distinguished from the behavior initializer by the lack of parens. If that is too subtle we could use a different name for the initialization accessor.

That's a possibility, but inserting initializer calls is a bit more heroic than what our current definite initialization implementation can do. That's not necessarily a showstopper, but it was a goal of the current design to avoid inserting accessor calls within inits at hard-to-predict places. (cc'ing ChrisL, to get his opinion on this as DI owner)

Makes sense. Getting the behavior I would like to see out of a `phase2Immutable` behavior would definitely require updates to the DI rules. I think the added safety makes it worth doing something in this area eventually, but it could be added later. I’m interested to hear what Chris thinks.

I think it's important that delayed initialization also accommodate initialization *after* init() returns. There are many cases where full initialization depends on a process outside of init's control, especially in Cocoa with objects and outlets set up from nib files.

I don’t really consider that to be initialization in the current sense. IMO, without extending our DI model, a “delayed” behavior for a property of type T is best modeled with storage for T?, which is implicitly initialized to nil. The getter would do a force unwrap of the optional, and the setter would check that the storage is nil before assigning over it.

If/when we have support for a more dynamic initialization (e.g. dynamic typestate) then we could expand this to use it, but I don’t see how it would provide a functionally different model.

This is pretty much what I mean, and what the [delayed] example from the proposal provides. Matthew's making a case for a more constrained form of 'delayed' that still prevents you from exiting 'init' without having initialized everything.

Ah ok. Well, such a thing could be built.

Yes, the use case for this is when you need to pass `self` to the initializer of one of your members. You can’t do this until phase 2, but it would be best if the compiler can still provide as much safety guarantee as possible.

-Matthew

···

On Jan 20, 2016, at 5:21 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 20, 2016, at 3:19 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Jan 20, 2016, at 3:17 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On Jan 20, 2016, at 2:08 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Will property behaviors work on computed properties? Because for now, we can change a storage to a computed property and a computed for a storage property without breaking anything.

I'm not sure exactly what you mean, but property behaviors are (by default) implementation details, so you can change the implementation of a property to computed to stored to behaviors without breaking API. If you publish a behavior as public, you'd be promising to use that behavior as part of the implementation forever.

-Joe

···

On Jan 21, 2016, at 1:23 PM, Wallacy <wallacyf@gmail.com> wrote:

Em qui, 21 de jan de 2016 às 00:44, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> escreveu:

> On Jan 20, 2016, at 6:12 PM, Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> wrote:
>
> Le 19 janv. 2016 à 21:38, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
>
>> One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.
>
> That's my general feeling too about this proposal. I just didn't know how to express what you said above.

I *did* somewhat strategically pick my examples to try to cover the breadth of different things I see someone wanting to do with this feature.

>
> To me this proposal feels like it's is about trying to find a solution to multiple problems at once. A new problem arise that looks like it could be solved by a behaviour, so the behaviour feature expands to accommodate it. It looks like the wrong approach to me.
>
> The correct approach in my opinion would be to try to make various parts of this proposal standalone, and allow them to combine when it makes sense. For instance, if you wanted to define a standalone feature for defining custom accessors that can be used everywhere, you wouldn't come with something that requires a behaviour annotation at the variable declaration. You'll come with something simpler that might looks like this:
>
> custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
> set { // ... by redefining the setter...
> willSet(newValue) // ...inserting a call to the accessor here...
> currentValue = newValue // ...before calling the underlying setter
> }
> }
> custom_acccessor didSet<T>(oldValue: T) {
> set {
> let oldValue = currentValue
> currentValue = newValue
> didSet(oldValue)
> }
> }
> custom_acccessor willChange<T>(newValue: T) {
> willSet {
> if currentValue != newValue {
> willChange(newValue)
> }
> }
> }
>
> Then at the declaration point you just directly use the globally accessible accessor:
>
> var myvar: Int {
> willChange { print("will change to \(newValue)") }
> }
>
> This fulfills at least one of the use cases. Can't we do the same treatment to each proposed use cases and see if there are other parts that can stand on their own?

I considered this approach. It works for behaviors that don't need to control a property's storage and only change the property's access behavior. To be fair, that covers a lot of ground, including things like observing, NSCopying, resetting, and locking synchronization. We would still need a feature, which could certainly be a different one, to generalize annotations that control the storage policy for decorated properties, which could cover things like laziness, indirect storage, unowned/weak-ness, dirty-tracking, C-style atomics, and pointer addressability—basically, anything where a plain old stored property of the API type isn't sufficient. (You could even throw get/set in this bucket, if you wanted to be super reductionist.) Finally, there's the feature to add operations on a *property* independent of its *type*, which interacts usefully with both other features—you need a way to reset a resettable or lazy property; maybe you want to bypass a synchronized property's lock in one place, etc. We'd like to improve on the "classic" answer of exposing an underlying ivar or property in these cases. If you want to break it down in micro-features, I guess there are three here:

1. Factoring out storage patterns,
2. Factoring out accessor patterns, and
3. Adding per-property operations.

(1) tends to be tightly coupled with (2)—if you're controlling storage, you almost certainly want to control the accessors over that storage. And (3) is useful with both (1) and (2). If there are separate features to be factored out here, I think they're very entangled features.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

You're glossing over some of the subtleties of initialization. Recall that a "plain old" stored property can be initialized out-of-line:

func foo() {
  var x: Int
  ...
  x = 1
}

struct Bar {
  var y: Int

  init(y: Int) {
    self.y = y
  }
}

which creates complications for your 'init { }' model. If initializers chain the way you describe, then either storage_behaviors can never be initialized out-of-line, which would be a regression for didSet/willSet applications, or we need to gain the ability for definite initialization to turn 'x = 1' into an 'init' call. However, since storage_behavior can refer to its initializer anywhere, not only during initialization, the initialization assignment potentially has to *capture* the RHS value to be evaluatable later, which would be surprising. If a behavior wants to be able to override a superclass property, as didSet/willSet can do today, then initialization is out of the behavior's control. These are the factors that influenced the "base property" design in my proposal—if you want behaviors that compose, they really *can't* meddle in the underlying base property's initialization. These use cases can be addressed instead by your accessor modifier mechanism; however, if you try to break apart storage behaviors and custom accessors, then both features suffer—storage behaviors can't require accessors to parameterize behavior, and custom accessors can't introduce new storage if needed to apply their implementation over the underlying property.

-Joe

···

On Jan 21, 2016, at 10:16 AM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 20 janv. 2016 à 21:44, Joe Groff <jgroff@apple.com> a écrit :

On Jan 20, 2016, at 6:12 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 19 janv. 2016 à 21:38, John McCall via swift-evolution <swift-evolution@swift.org> a écrit :

One of my worries about this proposal in general is that you’ve listed out half-a-dozen different uses and every single one seems to require a new twist on the core semantics.

That's my general feeling too about this proposal. I just didn't know how to express what you said above.

I *did* somewhat strategically pick my examples to try to cover the breadth of different things I see someone wanting to do with this feature.

To me this proposal feels like it's is about trying to find a solution to multiple problems at once. A new problem arise that looks like it could be solved by a behavior, so the behavior feature expands to accommodate it. It looks like the wrong approach to me.

The correct approach in my opinion would be to try to make various parts of this proposal standalone, and allow them to combine when it makes sense. For instance, if you wanted to define a standalone feature for defining custom accessors that can be used everywhere, you wouldn't come with something that requires a behavior annotation at the variable declaration. You'll come with something simpler that might looks like this:

custom_acccessor willSet<T>(newValue: T) { // define a custom accessor...
  set { // ... by redefining the setter...
    willSet(newValue) // ...inserting a call to the accessor here...
    currentValue = newValue // ...before calling the underlying setter
  }
}
custom_acccessor didSet<T>(oldValue: T) {
  set {
    let oldValue = currentValue
    currentValue = newValue
    didSet(oldValue)
  }
}
custom_acccessor willChange<T>(newValue: T) {
  willSet {
    if currentValue != newValue {
      willChange(newValue)
    }
  }
}

Then at the declaration point you just directly use the globally accessible accessor:

  var myvar: Int {
    willChange { print("will change to \(newValue)") }
  }

This fulfills at least one of the use cases. Can't we do the same treatment to each proposed use cases and see if there are other parts that can stand on their own?

I considered this approach. It works for behaviors that don't need to control a property's storage and only change the property's access behavior. To be fair, that covers a lot of ground, including things like observing, NSCopying, resetting, and locking synchronization. We would still need a feature, which could certainly be a different one, to generalize annotations that control the storage policy for decorated properties, which could cover things like laziness, indirect storage, unowned/weak-ness, dirty-tracking, C-style atomics, and pointer addressability—basically, anything where a plain old stored property of the API type isn't sufficient. (You could even throw get/set in this bucket, if you wanted to be super reductionist.) Finally, there's the feature to add operations on a *property* independent of its *type*, which interacts usefully with both other features—you need a way to reset a resettable or lazy property; maybe you want to bypass a synchronized property's lock in one place, etc. We'd like to improve on the "classic" answer of exposing an underlying ivar or property in these cases.

If you want to break it down in micro-features, I guess there are three here:

1. Factoring out storage patterns,
2. Factoring out accessor patterns, and
3. Adding per-property operations.

(1) tends to be tightly coupled with (2)—if you're controlling storage, you almost certainly want to control the accessors over that storage. And (3) is useful with both (1) and (2). If there are separate features to be factored out here, I think they're very entangled features.

No single language feature necessarily has to "control" the accessor. The accessor is really of a pile of "custom accessor code" wrapping one another, some in the variable declaration, some in the variable's behaviours, and so why not some others in a global accessor declaration? They just pile up on top of each other.

Last post was about (2), so let's try to attack (1) using this approach. Basically, this is going to be the same thing as your behaviour proposal, minus `var` inside of it, minus custom accessors defined inside of it, minus functions inside of it (we'll revisit that at the end), and where the base property is implicit (`currentValue`) and the initializer is implicit too (`initValue`).

I'll start with an "identity" behavior, a behavior that wraps a base property while actually doing nothing:

// identity for type T creates storage of type T
storage_behavior identity<T>: T {
  // regular initializer, works with eager or deferred initialization
  init {
    // initValue is implicitly defined in this scope
    currentValue = initValue // initializing the storage
  }
  get {
    return currentValue
  }
  set {
    currentValue = newValue
  }
}

Sometime you need to force the initializer to be eager or deferred. So we can do that with a keyword, in which case for `eager` the `initValue` becomes available anywhere inside the behavior, and for `deferred` it becomes available everywhere outside of `init`:

// identity for type T creates storage of type T
eager storage_behavior eagerIdentity<T>: T {
  // initValue is implicitly defined in this scope

  // eager initializer (takes not argument)
  init {
    currentValue = initValue // initializing the storage
  }
  get {
    return currentValue
  }
  set {
    currentValue = newValue
  }
}

Note at this point how this is basically the same syntax as with the `custom_accessor` examples from my last post, minus there is no accessor to call in `get` or `set`, plus you have the ability to affect the storage. The main difference for the user is that, since it's a behavior, you opt in to it by annotating the variable. Whereas in the case of `custom_accessor` you opt it by writing the custom accessor body inside of the variable's accessors.

Ok, now let's write lazy:

// lazy for type T creates storage of type T?
deferred storage_behavior lazy<T>: T? {
  init {
    // defered initValue not available in this scope
    currentValue = nil // initializing the storage
  }
  get {
    if currentValue == nil {
      currentValue = deferredInitValue
    }
    return currentValue! // converting from T? to T
  }
  set {
    currentValue = newValue // converting from T to T?
  }
}

Now let's make it atomic:

storage_behavior atomic<T>: Atomic<T> {
  init {
    currentValue = Atomic(initValue)
  }
  get {
    return currentValue.payload
  }
  set {
    currentValue.payload = newValue
  }
}

Note how I can avoid adding variables inside the behavior by just changing the underlying type to be some kind of wrapper of the original type. That makes the feature simpler.

We can also write synchronized (for variables in a context where self is Synchronizable):

storage_behavior synchronzied<T where Self == Synchronizable>: T {
  init {
    currentValue = initValue
  }
  get {
    return self.withLock {
      return currentValue
    }
  }
  set {
    self.withLock {
      currentValue = newValue
    }
  }
}

Here is a minimalistic logging behavior:

storage_behavior logging<T>: T {
  // implied "identity" version of init, get, and set.
  willChange {
    print("\(currentValue) will change for \(newValue)")
  }
}

This last example is interesting: it shows that you can use a globally-defined accessor inside of the behavior; you don't have to define a `set` or a `get` to make it useful. This logging behavior is basically just a shortcut for defining a variable like this:

  var myvar: Int {
    willChange {
      print("\(myvar) will change for \(newValue)")
    }
  }

  @logging var myvar2: Int // way less boilerplate!

So in the end, a storage behavior model defined like this brings to the table the `init` and the modified storage type. Otherwise it's just a nice and convenient way to avoid repeating boilerplate you can already write inside of a variable declaration. Which is great, because with less special rules the whole behaviour feature looks much more approachable now.

There was a proposal as well to get property accessor method access directly, without referencing the function.

If you had obj#myProperty.get() -> Val and obj#myProperty.set(_:Val) as functions (# syntax being a placeholder more than a proposal) perhaps then you could also have something like obj#myProperty.clear()

-DW

···

On Dec 23, 2015, at 10:07 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I agree that "myProperty.clear()" is very appealing, but it has the potential to be confusing when behavior methods are shadowed by members of the front-facing property. You'd still need a way to unambiguously refer to behavior methods when they're shadowed too.

-Joe

This is a preliminary draft, looking for feedback, thanks.

Introduction

There are many cases where you don’t want to deal with an out of range lookup in a array or dictionary. There is quite a lot of boilerplate code to check the range and return a value. This proposal addresses that by making a concise way of providing a default value in an array or dictionary. You quickly and safely want to get the value from an array or dictionary but not have to write a bunch of checks.

Swift-evolution thread: derived from ternary discussion.

Motivation

There are many times when you want to map value to another, the range of input values is beyond the array index. Typically you have to write code like this;

  let dayString = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]
  guard dayIndex > 0 && dayIndex < col..count else {
    return “invalid day"
  }
       dayString[dayIndex]

Or for a dictionary:

  let chineseNumber = [ “一” : “One”,
         “二” : ”Two”,
                “三” : ”Three”]

  guard let englishString = englishString else {
    return “out of range"
  }
  return englishString

Currently dictionaries only produce “nil” if not found so you must handle an optional.

Proposed solution:

There approach is to add default to the containers.

Array:
There is a new syntax which, allows you to choose a default:

let lookupDayOfWeek = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> “Invalid Day” ]

lookupDayOfWeek[1] -> “Monday”
lookupDayOfWeek[9] -> “Invalid Day”

The <> which is less than greater than. for the else portion, it indicates outside the range of the array as in less than the minimum index and greater than the highest index. It also looks like a diamond when run together so it stands out to clearly show that it is the default case and won’t be confused with the main array. In all other ways the array behaves the same, if you want to get the default value you could do this:

lookupDayOfWeek[ _ ] -> “Invalid Day”

Iterating an array with a default would not include the default:

  for day in lookupDayOfWeek {
    print(“Day \(day)”)
  }

You would need to do this to get the default (see below for slices):

  for day in lookupDayOfWeek[:_] {
    print(“Day \(day)”)
  }

or this would put the default first:

  for day in lookupDayOfWeek[_:] {
    print(“Day \(day)”)
  }

Unless there is a better suggestion for this, as this relies on slices.

Likewise contains() and other operations would not include the default as a result. However, if you were to copy an array with a default it would travel with the array:

let myOtherArray = lookupDayOfWeek
print(myOtherArray[_]) -> “Invalid Day”

If we were to adopt slices it would be possible to copy just the main values like this (based upon python slice syntax, speculative because slice syntax is not yet defined. If it is;):

let myOtherArray = lookupDayOfWeek[:]
print(myOtherArray) -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]

let myOtherArray = lookupDayOfWeek[:_]
print(myOtherArray -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> "Invalid Day”]

Dictionary:
For a dictionary, we use the _: to indicate none of the above, which is consistent with current dictionary syntax except that it adds the _ case..

let chineseToEnglishNumber = [ “一" : “One”,
             “二": ”Two”,
                              "三": ”Three”,
                                _: “Out of Range” ]

print(chineseToEnglishNumber[ “三”]) -> “Three"
print(chineseToEnglishNumber[“四”]) -> “Out of Range”

This concisely represents what to do if none of the values are usable. No more if let clauses or guards. Diamond <> could be supported here too, but that is optional for this proposal. The dictionary would be handled pretty much as it is today but the underscore-colon lets you give a default. Similarly, you can access the default value by doing this.

print(chineseToEnglishNumber[_]) -> “Out of Range”

print(chineseToEnglishNumber.contains(“None”)) -> would return false.
print(chineseToEnglishNumber) -> ["一": "2", "二": "1", "三": “3” <> “ Out of Range”]

Copying the dictionary would include the default but iterating it would not include the default:

for number in chineseToEnglishNumber {
  
}

to include it (this may need work):

for number in chineseToEnglishNumber[:_] {
  
}

since dictionaries are unordered it will not necessarily be at the end..

Alternatives considered

Other operators:
Tried single colon and double colon but did not stand out enough and colon might not work if we adopted slices.

Sets
Thought about sets but not sure that makes sense because you only test existence of a member usually, the following kind makes sense to a point:

let set = Set(“A”, “B”, “C” <> “D”)
set.contains(“B”) -> true
set.contains(“D”) -> false
set.contains(“F”) -> false

print(set) -> [“A”, “B”, “C”]

but typically you are just checking for existence or getting all values so having it return a default does not make sense.

Map
This came out of the ternary and switch discussions, this could be done with map defaults. If we don’t want to add it to the container types that might be a better way to go. See that thread for more details.

Few questions:

1 - This model has any optimization opportunities in mind? Like allow the compiler optimize calls over behaviors?

Yeah, the proposal is designed to avoid overhead for the behavior abstraction. By avoiding defining a type for behaviors, we avoid the runtime metadata cost of a generic type. By binding the initializers and accessors of the behavior specially, we avoid needing to store state in the instance of a type using a behavior to carry those parameters from initialization to access. For compile-time speed, we will likely need to generate functions and global data structures in debug builds, but these should all be inlineable in production builds.

2 - Will be possible compose behaviors and resuse, like:
typealias lazyObserver = [lazy,observer]

Not as stated. We could add behavior aliases or compositions if we wanted to later.

3 - "var behavior" will make unusual create any other property called behavior. Its not better make behavior a reserved word like "protocol" and use then? protocol is also a valid identifier.

The 'behavior' keyword can be contextual, since 'var behavior <identifier>' isn't otherwise legal syntax. You could still name a property or function 'behavior'.

4 - Will be provided any implementation of standard library of any other behavior like "lazy", "observer"?

I think we'll want to make 'lazy' and 'observer' into behaviors, but further standard library improvements should be discussed separately.

5 - How to handle with behavior name collision? If two modules define the same behavior name, can be handled using full name (module.behavior)?

Yes.

-Joe

···

On Jan 18, 2016, at 8:58 AM, Wallacy <wallacyf@gmail.com> wrote:

Em sáb, 16 de jan de 2016 às 17:27, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> escreveu:

On Jan 15, 2016, at 5:54 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Jan 15, 2016, at 7:45 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 15, 2016, at 6:42 AM, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

One more “how will this work?” question: optionals.

Specifically, consider something like this:

// annoyingly-long protocol:
protocol ControlExchanging {
  typealias Context
  func willTakeControlFrom(other: Self?, context: Context)
  func didTakeControlFrom(other: Self?, context: Context)

  func takeControl(context: Context)
  func cedeControl(context: Context)
  
  func willCedeControlTo(other: Self?, context: Context)
  func didCedeControlTo(other: Self?, context: Context)
}

var behavior exchangeState<Value:ControlExchanging where Self:Value.Context> : Value {
  var value: Value
  // here:
  set {
    let oldValue = value
    // boilerplate-choreography begins:
    newValue.willTakeControlFrom(oldValue, context: self)
    oldValue.willCedeControlTo(newValue, context: self)
    oldValue.cedeControl(self)
    value = newValue
    newValue.takeControl(self)
    oldValue.didCedeControlTo(newValue, context: self)
    newValue.didTakeControlFrom(oldValue, context: self)
  }
}

// numerous extraneous details omitted:
class GenericSwitchboard<Delegate:ControlExchanging were Delegate.Context == Self> {

  private(set) weak var [exchangeControl] delegate: Delegate? = nil

}

…which presumably won’t actually work unless I’ve either:

- added an additional implementation that’s typed-as `Value?`
- added a conditional-conformance for `ControlExchanging` to `Optional` (easy, but boilerplate)

….both of which are workable, neither of which feels optimal (and for the latter, consider also that in many cases such conformances may generally be undesirable).

Is there a trick/detail I’m missing here?

No, I think you've got it. This seems like a general problem to me, though; it'd be nice if protocol conformances could be easily forwarded, for instance from Optional<T> to T.

Any thoughts about how that might work when requirements have a return value? Or are you just referring to forwarding conformances when the protocol doesn’t have members with return values?

I don't have a great answer in mind, unfortunately. You're right that it only really makes sense for "sink"-like protocols, where none of the requirements produce a value, or theoretically for protocols where all results are of associated types that could be optionalized. The sink protocol use case comes up all the time, though, especially with delegates and callbacks, and is one the places where ObjC's nil-messaging behavior feels legitimate.

-Joe

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

You're glossing over some of the subtleties of initialization. Recall that a "plain old" stored property can be initialized out-of-line:

func foo() {
  var x: Int
  ...
  x = 1
}

struct Bar {
  var y: Int

  init(y: Int) {
    self.y = y
  }
}

which creates complications for your 'init { }' model. If initializers chain the way you describe, then either storage_behaviors can never be initialized out-of-line, which would be a regression for didSet/willSet applications, or we need to gain the ability for definite initialization to turn 'x = 1' into an 'init' call. However, since storage_behavior can refer to its initializer anywhere, not only during initialization, the initialization assignment potentially has to *capture* the RHS value to be evaluatable later, which would be surprising.

The idea was that inside a `storage_behavior` the `initValue` is only available inside of `init`. There are two exceptions: if the behavior is `eager` the `initValue` is available everywhere. Or if the behavior is `deferred` then the `initValue` is available everywhere *except* in `init`.

It seems I mistook what `eager` was for in your proposal. My interpretation of `eager` was that the value had to be specified inline with the variable declaration and could not be specified later in a constructor. Thus, its the same thing as `deferred` except that evaluating the initializer does not depend on `self` so you can use `initValue` truly everywhere including inside of `init`. Thus it appears my `eager` has no equivalent in your proposal while your `eager` is the same as not putting any modifier in front of `storage_behavior`.

I'm unsure now if there is any reason for my version of `eager` to exist. But it turns out you don't need my `eager` to implement resettable, all you need is to allow some storage for the initial value:

  storage_behavior resettable<T>: (initial: T, current: T) {
    init {
      currentValue = (initial: initValue, current: initValue)
    }
    func reset() {
      currentValue.current = currentValue.initial
    }
  }

If a behavior wants to be able to override a superclass property, as didSet/willSet can do today, then initialization is out of the behavior's control. These are the factors that influenced the "base property" design in my proposal—if you want behaviors that compose, they really *can't* meddle in the underlying base property's initialization. These use cases can be addressed instead by your accessor modifier mechanism; however, if you try to break apart storage behaviors and custom accessors, then both features suffer—storage behaviors can't require accessors to parameterize behavior, and custom accessors can't introduce new storage if needed to apply their implementation over the underlying property.

That's true. In other words, you can't override a superclass property with a behavior that touches `initValue` or defines the base storage type. As you say, we probably need another modifier keyword alongside `eager` and `deferred` to express that restriction so we can apply a behavior like `logging` when overriding.

And finally, to allow storage in overrides (both behaviors and accessors), all you need is to allow `var` and `let` inside of variable declarations, just like I suggested for functions:

  var myvar: Int {
    var observers: [Observer] =
    func addObserver(o: Observer) { observers.append(o) }
  }

then it automatically becomes possible to put all that boilerplate code inside of a behavior:

  storage_behavior observable<T>: T {
    var observers: [Observer] =
    func addObserver(o: Observer) { observers.append(o) }
  }

and it could be allowed in custom accessors too, although I can't come with a use case for this.

···

Le 21 janv. 2016 à 21:36, Joe Groff <jgroff@apple.com> a écrit :

--
Michel Fortin
https://michelf.ca

If a Property Behavior is 'officially' defined as a syntax sugar:

    var _name = ...
    var name: Type {
        get { return _name }
        set { _name = ... }
    }

Then allow:

    _name.behaviorMember ...

This is simple to implement, understand, and document.

···

Sent from my iPad

On 24 Dec 2015, at 5:30 PM, David Waite via swift-evolution <swift-evolution@swift.org> wrote:

There was a proposal as well to get property accessor method access directly, without referencing the function.

If you had obj#myProperty.get() -> Val and obj#myProperty.set(_:Val) as functions (# syntax being a placeholder more than a proposal) perhaps then you could also have something like obj#myProperty.clear()

-DW

On Dec 23, 2015, at 10:07 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I agree that "myProperty.clear()" is very appealing, but it has the potential to be confusing when behavior methods are shadowed by members of the front-facing property. You'd still need a way to unambiguously refer to behavior methods when they're shadowed too.

-Joe

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

While I like the idea of a default value I don't think that you need one as often.

On another thread ("Optional safe subscripting for arrays") something similar is being discussed where

array[safe: 6]

returns Optionals and your proposal is very similar to

array[safe: 6] ?? defaultValue
// or probably rewriting this to
array[6, default: defaultValue]

So these two proposals/threads could be merged.

In case of (Python like) slices I'm not sure whether the additional syntax pays off for its use.

Do you have any concrete plan how it could be implemented? As additional property/closure/DefaultProtocol?
I'm in favor of a closure: () -> Element
since it is like a generator where less weird behaviors can occur due to referencing classes. (Value types would work (almost) flawlessly with a stored property)

Another note: Could there be a more general way to use "_" as a language feature? So the proposal would be more likely to be accepted.

Best regards
- Maximilian

···

Am 17.01.2016 um 00:37 schrieb Paul Ossenbruggen via swift-evolution <swift-evolution@swift.org>:

This is a preliminary draft, looking for feedback, thanks.

Introduction

There are many cases where you don’t want to deal with an out of range lookup in a array or dictionary. There is quite a lot of boilerplate code to check the range and return a value. This proposal addresses that by making a concise way of providing a default value in an array or dictionary. You quickly and safely want to get the value from an array or dictionary but not have to write a bunch of checks.

Swift-evolution thread: derived from ternary discussion.

Motivation

There are many times when you want to map value to another, the range of input values is beyond the array index. Typically you have to write code like this;

  let dayString = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]
  guard dayIndex > 0 && dayIndex < col..count else {
    return “invalid day"
  }
       dayString[dayIndex]

Or for a dictionary:

  let chineseNumber = [ “一” : “One”,
         “二” : ”Two”,
                “三” : ”Three”]

  guard let englishString = englishString else {
    return “out of range"
  }
  return englishString

Currently dictionaries only produce “nil” if not found so you must handle an optional.

Proposed solution:

There approach is to add default to the containers.

Array:
There is a new syntax which, allows you to choose a default:

let lookupDayOfWeek = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> “Invalid Day” ]

lookupDayOfWeek[1] -> “Monday”
lookupDayOfWeek[9] -> “Invalid Day”

The <> which is less than greater than. for the else portion, it indicates outside the range of the array as in less than the minimum index and greater than the highest index. It also looks like a diamond when run together so it stands out to clearly show that it is the default case and won’t be confused with the main array. In all other ways the array behaves the same, if you want to get the default value you could do this:

lookupDayOfWeek[ _ ] -> “Invalid Day”

Iterating an array with a default would not include the default:

  for day in lookupDayOfWeek {
    print(“Day \(day)”)
  }

You would need to do this to get the default (see below for slices):

  for day in lookupDayOfWeek[:_] {
    print(“Day \(day)”)
  }

or this would put the default first:

  for day in lookupDayOfWeek[_:] {
    print(“Day \(day)”)
  }

Unless there is a better suggestion for this, as this relies on slices.

Likewise contains() and other operations would not include the default as a result. However, if you were to copy an array with a default it would travel with the array:

let myOtherArray = lookupDayOfWeek
print(myOtherArray[_]) -> “Invalid Day”

If we were to adopt slices it would be possible to copy just the main values like this (based upon python slice syntax, speculative because slice syntax is not yet defined. If it is;):

let myOtherArray = lookupDayOfWeek[:]
print(myOtherArray) -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]

let myOtherArray = lookupDayOfWeek[:_]
print(myOtherArray -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> "Invalid Day”]

Dictionary:
For a dictionary, we use the _: to indicate none of the above, which is consistent with current dictionary syntax except that it adds the _ case..

let chineseToEnglishNumber = [ “一" : “One”,
             “二": ”Two”,
                              "三": ”Three”,
                                _: “Out of Range” ]

print(chineseToEnglishNumber[ “三”]) -> “Three"
print(chineseToEnglishNumber[“四”]) -> “Out of Range”

This concisely represents what to do if none of the values are usable. No more if let clauses or guards. Diamond <> could be supported here too, but that is optional for this proposal. The dictionary would be handled pretty much as it is today but the underscore-colon lets you give a default. Similarly, you can access the default value by doing this.

print(chineseToEnglishNumber[_]) -> “Out of Range”

print(chineseToEnglishNumber.contains(“None”)) -> would return false.
print(chineseToEnglishNumber) -> ["一": "2", "二": "1", "三": “3” <> “ Out of Range”]

Copying the dictionary would include the default but iterating it would not include the default:

for number in chineseToEnglishNumber {
  
}

to include it (this may need work):

for number in chineseToEnglishNumber[:_] {
  
}

since dictionaries are unordered it will not necessarily be at the end..

Alternatives considered

Other operators:
Tried single colon and double colon but did not stand out enough and colon might not work if we adopted slices.

Sets
Thought about sets but not sure that makes sense because you only test existence of a member usually, the following kind makes sense to a point:

let set = Set(“A”, “B”, “C” <> “D”)
set.contains(“B”) -> true
set.contains(“D”) -> false
set.contains(“F”) -> false

print(set) -> [“A”, “B”, “C”]

but typically you are just checking for existence or getting all values so having it return a default does not make sense.

Map
This came out of the ternary and switch discussions, this could be done with map defaults. If we don’t want to add it to the container types that might be a better way to go. See that thread for more details.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I feel like, once you start stretching your design this way, we don't end up with something that's all that much simpler than what I've proposed. To help make review more digestible, I am planning to slim down the proposal a bit—I think we can subset out eager-vs-deferred initializers, composability via base properties, and extensions as future extensions to consider separately. That means the feature won't immediately be able to replace some of Swift 2's builtin features in all their subtleties, but it still gives us something useful that can be gradually generalized.

-Joe

···

On Jan 21, 2016, at 8:05 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 21 janv. 2016 à 21:36, Joe Groff <jgroff@apple.com> a écrit :

You're glossing over some of the subtleties of initialization. Recall that a "plain old" stored property can be initialized out-of-line:

func foo() {
var x: Int
...
x = 1
}

struct Bar {
var y: Int

init(y: Int) {
   self.y = y
}
}

which creates complications for your 'init { }' model. If initializers chain the way you describe, then either storage_behaviors can never be initialized out-of-line, which would be a regression for didSet/willSet applications, or we need to gain the ability for definite initialization to turn 'x = 1' into an 'init' call. However, since storage_behavior can refer to its initializer anywhere, not only during initialization, the initialization assignment potentially has to *capture* the RHS value to be evaluatable later, which would be surprising.

The idea was that inside a `storage_behavior` the `initValue` is only available inside of `init`. There are two exceptions: if the behavior is `eager` the `initValue` is available everywhere. Or if the behavior is `deferred` then the `initValue` is available everywhere *except* in `init`.

It seems I mistook what `eager` was for in your proposal. My interpretation of `eager` was that the value had to be specified inline with the variable declaration and could not be specified later in a constructor. Thus, its the same thing as `deferred` except that evaluating the initializer does not depend on `self` so you can use `initValue` truly everywhere including inside of `init`. Thus it appears my `eager` has no equivalent in your proposal while your `eager` is the same as not putting any modifier in front of `storage_behavior`.

I'm unsure now if there is any reason for my version of `eager` to exist. But it turns out you don't need my `eager` to implement resettable, all you need is to allow some storage for the initial value:

  storage_behavior resettable<T>: (initial: T, current: T) {
    init {
      currentValue = (initial: initValue, current: initValue)
    }
    func reset() {
      currentValue.current = currentValue.initial
    }
  }

If a behavior wants to be able to override a superclass property, as didSet/willSet can do today, then initialization is out of the behavior's control. These are the factors that influenced the "base property" design in my proposal—if you want behaviors that compose, they really *can't* meddle in the underlying base property's initialization. These use cases can be addressed instead by your accessor modifier mechanism; however, if you try to break apart storage behaviors and custom accessors, then both features suffer—storage behaviors can't require accessors to parameterize behavior, and custom accessors can't introduce new storage if needed to apply their implementation over the underlying property.

That's true. In other words, you can't override a superclass property with a behavior that touches `initValue` or defines the base storage type. As you say, we probably need another modifier keyword alongside `eager` and `deferred` to express that restriction so we can apply a behavior like `logging` when overriding.

And finally, to allow storage in overrides (both behaviors and accessors), all you need is to allow `var` and `let` inside of variable declarations, just like I suggested for functions:

  var myvar: Int {
    var observers: [Observer] =
    func addObserver(o: Observer) { observers.append(o) }
  }

then it automatically becomes possible to put all that boilerplate code inside of a behavior:

  storage_behavior observable<T>: T {
    var observers: [Observer] =
    func addObserver(o: Observer) { observers.append(o) }
  }

and it could be allowed in custom accessors too, although I can't come with a use case for this.

I think that both of these are new types or, at the very least, protocols.
If you are ok with using something other than subscript to access them, a
protocol is actually the best bet, in my opinion. With these additions,
Array acts as though it has *infinite* length and Dictionary acts as though
it holds all members that inhabit the Dictionary.Key type. That is
significant difference in behavior.
TJ

···

On Sat, Jan 16, 2016 at 8:16 PM, Maximilian Hünenberger < swift-evolution@swift.org> wrote:

While I like the idea of a default value I don't think that you need one
as often.

On another thread ("Optional safe subscripting for arrays") something
similar is being discussed where

array[safe: 6]

returns Optionals and your proposal is very similar to

array[safe: 6] ?? defaultValue
// or probably rewriting this to
array[6, default: defaultValue]

So these two proposals/threads could be merged.

In case of (Python like) slices I'm not sure whether the additional syntax
pays off for its use.

Do you have any concrete plan how it could be implemented? As additional
property/closure/DefaultProtocol?
I'm in favor of a closure: () -> Element
since it is like a generator where less weird behaviors can occur due to
referencing classes. (Value types would work (almost) flawlessly with a
stored property)

Another note: Could there be a more general way to use "_" as a language
feature? So the proposal would be more likely to be accepted.

Best regards
- Maximilian

Am 17.01.2016 um 00:37 schrieb Paul Ossenbruggen via swift-evolution < > swift-evolution@swift.org>:

This is a preliminary draft, looking for feedback, thanks.

*Introduction*

There are many cases where you don’t want to deal with an out of range
lookup in a array or dictionary. There is quite a lot of boilerplate code
to check the range and return a value. This proposal addresses that by
making a concise way of providing a default value in an array or
dictionary. You quickly and safely want to get the value from an array or
dictionary but not have to write a bunch of checks.

Swift-evolution thread: derived from ternary discussion.

*Motivation*

There are many times when you want to map value to another, the range of
input values is beyond the array index. Typically you have to write code
like this;

let dayString = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”,
“Friday”, “Saturday”]
guard dayIndex > 0 && dayIndex < col..count else {
return “invalid day"
}
       dayString[dayIndex]

Or for a dictionary:

let chineseNumber = [ “一” : “One”,
     “二” : ”Two”,
      “三” : ”Three”]

guard let englishString = englishString else {
return “out of range"
}
return englishString

Currently dictionaries only produce “nil” if not found so you must handle
an optional.

*Proposed solution:*

There approach is to add default to the containers.

*Array:*
There is a new syntax which, allows you to choose a default:

let lookupDayOfWeek = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”,
“Thursday”, “Friday”, “Saturday” <> “Invalid Day” ]

lookupDayOfWeek[1] -> “Monday”
lookupDayOfWeek[9] -> “Invalid Day”

The <> which is less than greater than. for the else portion, it
indicates outside the range of the array as in less than the minimum index
and greater than the highest index. It also looks like a diamond when run
together so it stands out to clearly show that it is the default case and
won’t be confused with the main array. In all other ways the array behaves
the same, if you want to get the default value you could do this:

lookupDayOfWeek[ _ ] -> “Invalid Day”

Iterating an array with a default would not include the default:

for day in lookupDayOfWeek {
print(“Day \(day)”)
}

You would need to do this to get the default (see below for slices):

for day in lookupDayOfWeek[:_] {
print(“Day \(day)”)
}

or this would put the default first:

for day in lookupDayOfWeek[_:] {
print(“Day \(day)”)
}

Unless there is a better suggestion for this, as this relies on slices.

Likewise contains() and other operations would not include the default as
a result. However, if you were to copy an array with a default it would
travel with the array:

let myOtherArray = lookupDayOfWeek
print(myOtherArray[_]) -> “Invalid Day”

If we were to adopt slices it would be possible to copy just the main
values like this (based upon python slice syntax, speculative because slice
syntax is not yet defined. If it is;):

let myOtherArray = lookupDayOfWeek[:]
print(myOtherArray) -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”,
“Thursday”, “Friday”, “Saturday”]

let myOtherArray = lookupDayOfWeek[:_]
print(myOtherArray -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”,
“Thursday”, “Friday”, “Saturday” <> "Invalid Day”]

*Dictionary:*
For a dictionary, we use the _: to indicate none of the above, which is
consistent with current dictionary syntax except that it adds the _ case..

let chineseToEnglishNumber = [ “一" : “One”,
         “二": ”Two”,
                      "三": ”Three”,
                        _: “Out of Range” ]

print(chineseToEnglishNumber[ “三”]) -> “Three"
print(chineseToEnglishNumber[“四”]) -> “Out of Range”

This concisely represents what to do if none of the values are usable. No
more if let clauses or guards. Diamond <> could be supported here too,
but that is optional for this proposal. The dictionary would be handled
pretty much as it is today but the underscore-colon lets you give a
default. Similarly, you can access the default value by doing this.

print(chineseToEnglishNumber[_]) -> “Out of Range”

print(chineseToEnglishNumber.contains(“None”)) -> would return false.
print(chineseToEnglishNumber) -> ["一": "2", "二": "1", "三": “3” <> “ Out
of Range”]

Copying the dictionary would include the default but iterating it would
not include the default:

for number in chineseToEnglishNumber {
}

to include it (this may need work):

for number in chineseToEnglishNumber[:_] {
}

since dictionaries are unordered it will not necessarily be at the end..

*Alternatives considered*

*Other operators:*
Tried single colon and double colon but did not stand out enough and colon
might not work if we adopted slices.

*Sets*
Thought about sets but not sure that makes sense because you only test
existence of a member usually, the following kind makes sense to a point:

let set = Set(“A”, “B”, “C” <> “D”)
set.contains(“B”) -> true
set.contains(“D”) -> false
set.contains(“F”) -> false

print(set) -> [“A”, “B”, “C”]

but typically you are just checking for existence or getting all values so
having it return a default does not make sense.

*Map*
This came out of the ternary and switch discussions, this could be done
with map defaults. If we don’t want to add it to the container types that
might be a better way to go. See that thread for more details.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I am not sure I would be in favor of additional syntax for something like this. I think that those who want it could write extensions for Array and Dictionary and do the following instead:

let value = array.objectAtIndex(i, default: defaultValue)

let value = dict.objectForKey(key, default: defaultValue)

···

On Jan 16, 2016, at 8:28 PM, T.J. Usiyan via swift-evolution <swift-evolution@swift.org> wrote:

I think that both of these are new types or, at the very least, protocols. If you are ok with using something other than subscript to access them, a protocol is actually the best bet, in my opinion. With these additions, Array acts as though it has *infinite* length and Dictionary acts as though it holds all members that inhabit the Dictionary.Key type. That is significant difference in behavior.
TJ

On Sat, Jan 16, 2016 at 8:16 PM, Maximilian Hünenberger <swift-evolution@swift.org> wrote:
While I like the idea of a default value I don't think that you need one as often.

On another thread ("Optional safe subscripting for arrays") something similar is being discussed where

array[safe: 6]

returns Optionals and your proposal is very similar to

array[safe: 6] ?? defaultValue
// or probably rewriting this to
array[6, default: defaultValue]

So these two proposals/threads could be merged.

In case of (Python like) slices I'm not sure whether the additional syntax pays off for its use.

Do you have any concrete plan how it could be implemented? As additional property/closure/DefaultProtocol?
I'm in favor of a closure: () -> Element
since it is like a generator where less weird behaviors can occur due to referencing classes. (Value types would work (almost) flawlessly with a stored property)

Another note: Could there be a more general way to use "_" as a language feature? So the proposal would be more likely to be accepted.

Best regards
- Maximilian

Am 17.01.2016 um 00:37 schrieb Paul Ossenbruggen via swift-evolution <swift-evolution@swift.org>:

This is a preliminary draft, looking for feedback, thanks.

Introduction

There are many cases where you don’t want to deal with an out of range lookup in a array or dictionary. There is quite a lot of boilerplate code to check the range and return a value. This proposal addresses that by making a concise way of providing a default value in an array or dictionary. You quickly and safely want to get the value from an array or dictionary but not have to write a bunch of checks.

Swift-evolution thread: derived from ternary discussion.

Motivation

There are many times when you want to map value to another, the range of input values is beyond the array index. Typically you have to write code like this;

  let dayString = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]
  guard dayIndex > 0 && dayIndex < col..count else {
    return “invalid day"
  }
       dayString[dayIndex]

Or for a dictionary:

  let chineseNumber = [ “一” : “One”,
         “二” : ”Two”,
                “三” : ”Three”]

  guard let englishString = englishString else {
    return “out of range"
  }
  return englishString

Currently dictionaries only produce “nil” if not found so you must handle an optional.

Proposed solution:

There approach is to add default to the containers.

Array:
There is a new syntax which, allows you to choose a default:

let lookupDayOfWeek = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> “Invalid Day” ]

lookupDayOfWeek[1] -> “Monday”
lookupDayOfWeek[9] -> “Invalid Day”

The <> which is less than greater than. for the else portion, it indicates outside the range of the array as in less than the minimum index and greater than the highest index. It also looks like a diamond when run together so it stands out to clearly show that it is the default case and won’t be confused with the main array. In all other ways the array behaves the same, if you want to get the default value you could do this:

lookupDayOfWeek[ _ ] -> “Invalid Day”

Iterating an array with a default would not include the default:

  for day in lookupDayOfWeek {
    print(“Day \(day)”)
  }

You would need to do this to get the default (see below for slices):

  for day in lookupDayOfWeek[:_] {
    print(“Day \(day)”)
  }

or this would put the default first:

  for day in lookupDayOfWeek[_:] {
    print(“Day \(day)”)
  }

Unless there is a better suggestion for this, as this relies on slices.

Likewise contains() and other operations would not include the default as a result. However, if you were to copy an array with a default it would travel with the array:

let myOtherArray = lookupDayOfWeek
print(myOtherArray[_]) -> “Invalid Day”

If we were to adopt slices it would be possible to copy just the main values like this (based upon python slice syntax, speculative because slice syntax is not yet defined. If it is;):

let myOtherArray = lookupDayOfWeek[:]
print(myOtherArray) -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”]

let myOtherArray = lookupDayOfWeek[:_]
print(myOtherArray -> [“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday” <> "Invalid Day”]

Dictionary:
For a dictionary, we use the _: to indicate none of the above, which is consistent with current dictionary syntax except that it adds the _ case..

let chineseToEnglishNumber = [ “一" : “One”,
             “二": ”Two”,
                              "三": ”Three”,
                                _: “Out of Range” ]

print(chineseToEnglishNumber[ “三”]) -> “Three"
print(chineseToEnglishNumber[“四”]) -> “Out of Range”

This concisely represents what to do if none of the values are usable. No more if let clauses or guards. Diamond <> could be supported here too, but that is optional for this proposal. The dictionary would be handled pretty much as it is today but the underscore-colon lets you give a default. Similarly, you can access the default value by doing this.

print(chineseToEnglishNumber[_]) -> “Out of Range”

print(chineseToEnglishNumber.contains(“None”)) -> would return false.
print(chineseToEnglishNumber) -> ["一": "2", "二": "1", "三": “3” <> “ Out of Range”]

Copying the dictionary would include the default but iterating it would not include the default:

for number in chineseToEnglishNumber {
  
}

to include it (this may need work):

for number in chineseToEnglishNumber[:_] {
  
}

since dictionaries are unordered it will not necessarily be at the end..

Alternatives considered

Other operators:
Tried single colon and double colon but did not stand out enough and colon might not work if we adopted slices.

Sets
Thought about sets but not sure that makes sense because you only test existence of a member usually, the following kind makes sense to a point:

let set = Set(“A”, “B”, “C” <> “D”)
set.contains(“B”) -> true
set.contains(“D”) -> false
set.contains(“F”) -> false

print(set) -> [“A”, “B”, “C”]

but typically you are just checking for existence or getting all values so having it return a default does not make sense.

Map
This came out of the ternary and switch discussions, this could be done with map defaults. If we don’t want to add it to the container types that might be a better way to go. See that thread for more details.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution