[Pitch] Adding safety to arrays


(James Froggatt) #1

Array's (Collection's?) `.first` property already returns element 0 as an optional, so your first use case is already covered.

For the second point, I'll back this up with a less hacky example: accessing the detail viewcontroller, if present, from a SplitViewController loaded from a storyboard.

Your string parsing example, and perhaps parsing in general, seem like the strongest use-case for this feature.

I would also like to see an optional subscript or method added to Array, but since this is additive I'm not expecting it any time soon.

------------ Begin Message ------------
Group: gmane.comp.lang.swift.evolution
MsgID: <CAHO0oD9Odg4WPe2jOTqJz_kW=w3V0+H=9Nb0UJQT2NkOurZGXQ@mail.gmail.com>

To respond to one of the main points Ive seen, which is that this isnt
necessary for arrays because the index youre 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.

Thats 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 Ive 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 Im
fairly sure that these video assets will all contain a video track, its
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)")
}

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
views 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 Ill 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 its 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 isnt encouraged, but its
an example of some of the more finicky UI work that in reality is often
required on iOS in order to achieve certain goals.

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 youre saying ok, so I know therell be 2
components I require later on, so Ill check to make sure I have at least 2
components, and then after that Ill 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 dont exist then return nil.

···

On 17 April 2017 at 20:22:10, Riley Testut (rileytestut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org) wrote:

Dynamic programming comes to mind.

Wouldnt you then be able to use Array(repeating:count:) and repeat 0 (or
something else) to achieve this then?

Yes, less performant than allocing array (since we need to fill in default
values), but doing otherwise would go against Swifts 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-tskU2QEWZ+aB+jHODAdFcQ@public.gmane.org> wrote:

Dynamic programming comes to mind.

Saagar Jha

On Apr 16, 2017, at 19:33, Riley Testut <rileytestut-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> 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 = 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-tskU2QEWZ+aB+jHODAdFcQ@public.gmane.org> 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-m3FHrko0VLzYtjvyW6yDsg@public.gmane.org> wrote:

Personally, the only valid use-case I can think of is when you want to
initialise an Arrays 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-m3FHrko0VLzYtjvyW6yDsg@public.gmane.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 dont
understand what an Array is. There are two use-cases for an Array:

1) As a string of items, dont 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 were talking about.

Basically, the interesting part of a statement such as let someValue myArray[2] is: why index 2? Whats so special about that element; why
couldn't someValue be the item at any index N instead? Its 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 Arrays 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-m3FHrko0VLzYtjvyW6yDsg@public.gmane.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

------------- End Message -------------


Add accessor with bounds check to Array