[Pitch] Adding safety to arrays


(Karl) #1

Responses inline

···

  
On May 7, 2017 at 15:16, <Andrew Hart (mailto:andrew@projectdent.com)> wrote:
  
To respond to one of the main points I’ve seen, which is that this isn’t necessary for arrays because the index you’re using is often tied to data related to the array, such as in the example I gave originally, where in `cellForRowAtIndexPath`, the index path is probably informed by the length of the array.
  
That’s true for data-backed code, but on iOS arrays are used in other scenarios. I wanted to provide 3 additional examples where optional arrays would be helpful that I’ve run across recently:
  
1. When determining the video dimensions, frame rate or other property of an AVAsset which you know is a video, you need to first use assetTracks(withMediaType: String), which returns an array. While I’m fairly sure that these video assets will all contain a video track, it’s not a certainty, so to be safe I need to check that one exists. Currently my code looks like this:
  
let videoTracks = asset.tracks(withMediaType: AVMediaTypeVideo)
  
if videoTracks.count > 0 {
  
       let videoTrack = videoTracks[0]
  
       print("dimensions: \(videoTrack.naturalSize)")
  
}
  
With optional arrays, this could be shortened to this:
  
if let videoTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0] {
  
       print("dimensions: \(videoTrack.naturalSize)")
  
}
  
In this case, where you want a nil if the Array is empty, try .first/.last
  
2. UIVisualEffect can display a blurred view on top of underlying views. If you wish to add a tint color, it's simply added on top of the blur view's own background color (by default, 0.97 white with 0.8 alpha) which is provided by a subview. Your tint color being placed on top of the blur view’s background color therefore degrades how that color affects the content underneath, and also the overall opacity of the content underneath. You might also not want the overlay to be as strong as 0.8. To get around this, I might customise the UIVisualEffectView to display my own base color displayed over the blur. I know that the background color is set in the 2nd subview of visualEffectView, so I’ll modify it like so:
  
if visualEffectView.subviews.count >= 2 {
  
       let tintedView = self.visualEffectView.subviews[1]
  
       tintedView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
  
}
  
The checking for the subviews count is important here - while it’s possible that iOS may change the implementation of UIVisualEffectViews in future, and break my UI, I would much prefer that over the app crashing.
  
With optional arrays, this UI code could be shortened to this:
  
visualEffectView.subviews[1]?.backgroundColor = UIColor.red.withAlphaComponent(0.5)
  
Obviously this kind of manipulation is hacky and isn’t encouraged, but it’s an example of some of the more finicky UI work that in reality is often required on iOS in order to achieve certain goals.
  
I would refer you to my previous email, specifically point #2: you have some external knowledge about what to expect in "subviews" at the second position. In this case, I would really expect you to be commenting why you are getting the subview at index 1, rather than .first, .last, or any other arbitrary subview. As you say, Apple could break your App by simply adding another private view. If you're doing these kind of hacks, and sometimes they are necessary, you should be wrapping them behind conditions which test as many assumptions as possible (e.g. What exactly do you expect at subviews[1]? What about subviews[2]? Is there any way to rewrite your code so that you derive the index by inspecting the view hierarchy at runtime instead of hard-coding exactly how it should look?)
  
Eventually, you may get to the point where it's no longer a hack, but actually a very clever, resilient use of documented guarantees. That's where we'd all like to be.
  
3. I also think optional arrays can be used for clarity of purpose, in the same way that dictionary keys are. In a different scenario, such as when attempting to extract data from a string, this is how code might currently look:
  
let components = coordinatesString.components(separatedBy: ",")
  
if components.count < 2 {
  
       return nil
  
}
  
let latitudeString = components[0]
  
let longitudeString = components[1]
  
Notice that in advance you’re saying “ok, so I know there’ll be 2 components I require later on, so I’ll check to make sure I have at least 2 components, and then after that I’ll explain what those are for”. Instead, it could look like this:
  
let components = coordinatesString.components(separatedBy: ",”)
  
let latitudeString = components[0]
let longitudeString = components[1]
  
if latitudeString == nil || longitudeString == nil {
  
       return nil
  
}
  
The intent is much clearer here. You want to extract latitude and longitude, but if either of those don’t exist then return nil.
  
I don't see how the latter example is significantly better than the first. It doesn't reduce code size or improve legibility, and I fear any general thing we might try to add would be overused and ultimately degrade the standard library. If you find Array bounds checking too onerous, you can wrap it to build your own type or extend it to provide convenient shorthands. I can understand why you might want to add these things sometimes, but in general I think it's not a good change for the standard library.
  
On 17 April 2017 at 20:22:10, Riley Testut (rileytestut@gmail.com (mailto:rileytestut@gmail.com)) wrote:

>
>
>
>
> >
> > Dynamic programming comes to mind.
> >
>
>
>
>
> Wouldn’t you then be able to use Array(repeating:count:) and repeat 0 (or something else) to achieve this then?
>
>
>
> Yes, less performant than alloc’ing array (since we need to fill in default values), but doing otherwise would go against Swift’s safety model. If you truly wanted that behavior, you can use the UnsafePointer methods anyway (AFAIK).
>
>
>
> >
> > On Apr 16, 2017, at 9:18 PM, Saagar Jha <saagar@saagarjha.com (mailto:saagar@saagarjha.com)> wrote:
> >
> >
> >
> > Dynamic programming comes to mind.
> >
> >
> >
> > Saagar Jha
> >
> >
> >
> >
> > >
> > > On Apr 16, 2017, at 19:33, Riley Testut <rileytestut@gmail.com (mailto:rileytestut@gmail.com)> wrote:
> > >
> > >
> > >
> > >
> > >
> > >
> > > My bad, should have phrased my response better :^)
> > >
> > >
> > >
> > > Under what circumstances would you need to be able to assign elements in an array out of order, while also requiring Array size/performance? (Genuinely curious, not trying to attack).
> > >
> > >
> > >
> > > IMO, if the differences between Array and Dictionary would cause that much of an issue for your implementation, my guess is you have more important priorities than the need to assign elements out-of-order :wink: I don't think we'd need to add another type to the standard library for this use case.
> > >
> > >
> > > On Apr 16, 2017, at 11:22 AM, Saagar Jha <saagar@saagarjha.com (mailto:saagar@saagarjha.com)> wrote:
> > >
> > >
> > > >
> > > > A Dictionary uses a lot more space than an Array, though, and allow for bogus keys like “-1”, etc.
> > > >
> > > >
> > > >
> > > > Saagar Jha
> > > >
> > > >
> > > >
> > > >
> > > > >
> > > > > On Apr 16, 2017, at 10:34, Riley Testut via swift-evolution <swift-evolution@swift.org (mailto:swift-evolution@swift.org)> wrote:
> > > > >
> > > > >
> > > > >
> > > > >
> > > > >
> > > > >
> > > > >
> > > > > > Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.
> > > > >
> > > > >
> > > > > Wouldn't the same functionality be accomplished by a Dictionary with Int as the key type?
> > > > >
> > > > >
> > > > > On Apr 14, 2017, at 10:00 AM, Karl Wagner via swift-evolution <swift-evolution@swift.org (mailto:swift-evolution@swift.org)> wrote:
> > > > >
> > > > >
> > > > > >
> > > > > >
> > > > > > > I'd actually say the #1 reason not to add this feature is that a lot of developers don't seem to understand this, and they're likely to use the feature to make their code try to continue in the face of programmer error instead of trapping like it properly should. A program in an inconsistent state is dangerous; best to stop it quickly before it does some damage.)
> > > > > >
> > > > > > Right, so I think the reason is actually that a lot of developers don’t understand what an Array is. There are two use-cases for an Array:
> > > > > >
> > > > > > 1) As a string of items, don’t care about the length. The maximum prior knowledge you can have is that the order may or may not be significant. This includes operations like iteration, mapping, reducing and filtering.
> > > > > > 2) As a string of items of specific length. You have prior knowledge about what you expect to find at each location. This includes operations like random-access subscripting, which is what we’re talking about.
> > > > > >
> > > > > > Basically, the interesting part of a statement such as “let someValue = myArray[2]” is: why index 2? What’s so special about that element; why couldn't someValue be the item at any index N instead? It’s because we know to expect something of special significance at index 2.
> > > > > >
> > > > > > In that case, the only time myArray[2] will fail is when your prior knowledge breaks down. The type-system has no way to encode and check for the length of an Array, and that has allowed somebody to pass in a bad value. So what to do?
> > > > > >
> > > > > > A) If you absolutely require a value for myArray[2]: Insert a precondition check.
> > > > > > B) If you can still continue without myArray[2]: Check the length of the Array. Your logic will be branching anyway in this case, to account for the value (and subsequent values) being/not being present.
> > > > > >
> > > > > >
> > > > > > Personally, the only valid use-case I can think of is when you want to initialise an Array’s elements out-of-order - i.e., you want to set a value for myArray[2] despite myArray[0] and [1] not being populated. In that case, it would be better to have some kind of SparseArray type, and for us to have a proper API for unsafe initialisation of stdlib types.
> > > > > >
> > > > > > - Karl
> > > > > > _______________________________________________
> > > > > > swift-evolution mailing list
> > > > > > swift-evolution@swift.org (mailto:swift-evolution@swift.org)
> > > > > > https://lists.swift.org/mailman/listinfo/swift-evolution
> > > > > > _______________________________________________
> > > > > swift-evolution mailing list
> > > > > swift-evolution@swift.org (mailto:swift-evolution@swift.org)
> > > > > https://lists.swift.org/mailman/listinfo/swift-evolution
> > > > >
> > > >
> >
>