[swift-evolution] Shouldn't ".withUnsafeBufferPointer" and ".withUnsafeMutableBufferPointer" be parts of protocols?

Daryle Walker darylew at mac.com
Sun Feb 5 12:56:35 CST 2017


> On Jan 27, 2017, at 7:36 PM, Zach Waldowski via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I like the sound of it so far, but my first major thought is that isn't it modeling a "has-a" relationship instead of an "is-a"? The buffer methods indicate that the data type *can* be represented as a buffer for the duration of the method call, but may not necessarily be before or after. Such a semantic distinction would also allow Data and DispatchData, as well as other theoretical data structures like a Deque (I think?) to participate.

I copied those parts of the documentation from Array. Unlike ArraySlice and ContiguousArray, due to NSArray compatibility, continuous storage for Array isn’t prepared until needed. I didn’t mean to imply that the relationship isn’t IS-A. Although now I don’t see any reason to ban ephemeral storage, the conforming type should act as if its element state is consistent between calls. (A conforming type that generates a new random block before each call, even ignoring previous mutations, isn’t very useful.)

— 
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com 

> Cheers!
>   Zachary Waldowski
>   zach at waldowski.me <mailto:zach at waldowski.me>
> 
> 
> On Fri, Jan 27, 2017, at 06:40 PM, Daryle Walker via swift-evolution wrote:
>> I was perusing the library for array ideas, and noticed that several types had methods in common, but without a grouping protocol. Shouldn’t that be fixed?
>> 
>> (Oh, if multiple protocols have an associated-type with the same name, is that a problem? Is it a problem if they resolve differently for each protocol attached to a given type? I’m asking because these protocols reuse Sequence’s Element for their own purpose.)
>> 
>> 
>> Formal Protocol for Contiguous Storage Visitation
>> Proposal: SE-NNNN <>
>> Authors: Daryle Walker <https://github.com/CTMacUser>
>> Review Manager: TBD
>> Status: Awaiting review
>> During the review process, add the following fields as needed:
>> 
>> Decision Notes: Rationale <https://lists.swift.org/pipermail/swift-evolution/>, Additional Commentary <https://lists.swift.org/pipermail/swift-evolution/>
>> Bugs: SR-NNNN <https://bugs.swift.org/browse/SR-NNNN>, SR-MMMM <https://bugs.swift.org/browse/SR-MMMM>
>> Previous Revision: 1 <https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md>
>> Previous Proposal: SE-XXXX <>
>> Introduction
>> The standard library types Array, ArraySlice, and ContiguousArray have an interface for visiting their elements as a contiguous block of memory (arranging said elements to that configuration first if necessary). These methods are all the same, but not under a common protocol (i.e. seeming to match by "coincidence").
>> 
>> This proposal seeks to correct that with two new protocols that these types will implement.
>> 
>> Swift-evolution thread: Discussion thread topic for that proposal <https://lists.swift.org/pipermail/swift-evolution/>
>> Motivation
>> Just adding these protocols for consistency is relatively minor, but they may be used for other types. Particularly, they would be needed if fixed-sized arrays are added to the language.
>> 
>> Proposed solution
>> The library array types will follow the MutableContiguousBlockprotocol, which inherits from the ContiguousBlock protocol.
>> 
>> extension Array: MutableContiguousBlock {
>> }
>> 
>> extension ArraySlice: MutableContiguousBlock {
>> }
>> 
>> extension ContiguousArray: MutableContiguousBlock {
>> }
>> For example, a repackaging of the library's example code:
>> 
>> func change<T: MutableContiguousBlock>(object: inout T) -> T.Element where T.Element: Integer {
>>     let sum = object.withUnsafeBufferPointer { (buffer) -> T.Element in
>>         var result: T.Element = 0
>>         for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
>>             result += buffer[i]
>>         }
>>         return result
>>     }
>>     object.withUnsafeMutableBufferPointer { buffer in
>>         for j in stride(from: buffer.startIndex, to: buffer.endIndex - 1, by: 2) {
>>             swap(&buffer[j], &buffer[j + 1])
>>         }
>>     }
>>     return sum
>> }
>> 
>> var numbers = [1, 2, 3, 4, 5]
>> print(change(object: &numbers))  // 9
>> print(numbers)                   // [2, 1, 4, 3, 5]
>> Detailed design
>> /**
>>     Visitation protocol of the receiver's contiguous storage of immutable elements.
>>  */
>> protocol ContiguousBlock {
>> 
>>     /// Inferred alias to the element type to visit
>>     associatedtype Element
>> 
>>     /**
>>         Calls a closure with a pointer to the receiver's contiguous storage. If no such storage exists, it is first created.
>> 
>>         Often, the optimizer can eliminate bounds checks within an array algorithm, but when that fails, invoking the same algorithm on the buffer pointer passed into your closure lets you trade safety for speed.
>> 
>>         The following example shows how you can iterate over the contents of the buffer pointer:
>> 
>>             let numbers = [1, 2, 3, 4, 5]
>>             let sum = numbers.withUnsafeBufferPointer { buffer -> Int in
>>                 var result = 0
>>                 for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
>>                     result += buffer[i]
>>                 }
>>                 return result
>>             }
>>             // 'sum' == 9
>> 
>>         - Parameter body: A closure with an `UnsafeBufferPointer` parameter that points to the contiguous storage for the receiver. If `body` has a return value, it is used as the return value for this method. The pointer argument is valid only for the duration of the closure's execution.
>> 
>>         - Returns: The return value of the `body` closure parameter, if any.
>> 
>>         - SeeAlso: Swift.UnsafeBufferPointer
>>      */
>>     func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R
>> 
>> }
>> 
>> /**
>>     Visitation protocol of the receiver's contiguous storage of elements, allowing mutation.
>>  */
>> protocol MutableContiguousBlock: ContiguousBlock {
>> 
>>     /**
>>         Calls the given closure with a pointer to the receiver's mutable contiguous storage. If no such storage exists, it is first created.
>> 
>>         Often, the optimizer can eliminate bounds checks within an array algorithm, but when that fails, invoking the same algorithm on the buffer pointer passed into your closure lets you trade safety for speed.
>> 
>>         The following example shows modifying the contents of the `UnsafeMutableBufferPointer` argument to `body` alters the contents of the receiver:
>> 
>>             var numbers = [1, 2, 3, 4, 5]
>>             numbers.withUnsafeMutableBufferPointer { buffer in
>>                 for i in stride(from: buffer.startIndex, to: buffer.endIndex - 1, by: 2) {
>>                     swap(&buffer[i], &buffer[i + 1])
>>                 }
>>             }
>>             print(numbers)
>>             // Prints "[2, 1, 4, 3, 5]"
>> 
>>         - Warning: Do not rely on anything about `self` (the receiver of this method) during the execution of the `body` closure: It may not appear to have its correct value. Instead, use only the `UnsafeMutableBufferPointer` argument to `body`.
>> 
>>         - Parameter body: A closure with an `UnsafeMutableBufferPointer` parameter that points to the contiguous storage for the receiver. If `body` has a return value, it is used as the return value for this method. The pointer argument is valid only for the duration of the closure's execution.
>> 
>>         - Returns: The return value of the `body` closure parameter, if any.
>> 
>>         - SeeAlso: withUnsafeBufferPointer, Swift.UnsafeMutableBufferPointer
>>      */
>>     mutating func withUnsafeMutableBufferPointer<R>(_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R) rethrows -> R
>> 
>> }
>> I don't know how the 3 library types implement this methods, but we know it can be done. If added as an implicit interface to built-in fixed-sized arrays, the implementers can use similar techniques or compiler magic. I don't know the difficulty to adapt these interfaces with third-party code.
>> 
>> Source compatibility
>> The changes are strictly additive, and at the library level, so there should be no impact to existing code.
>> 
>> Effect on ABI stability
>> These changes should not affect ABI stability.
>> 
>> Effect on API resilience
>> As just stated, the addition of two protocol names to the API shouldn't affect the ABI.
>> 
>> Alternatives considered
>> The main alternative is to do nothing. If fixed-sized arrays are later added, they could have the same methods too, but there would still be no common link.
>> 
>> Another alternative would make MutableContiguousBlock not inherit from ContiguousBlock, meaning the former would need to define an Element associated type. But don't see a need for a protocol with a read-write method without its read-only counterpart available.
>> 
>> 
>>>> Daryle Walker
>> Mac, Internet, and Video Game Junkie
>> darylew AT mac DOT com 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170205/e422eaf5/attachment.html>


More information about the swift-evolution mailing list