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

Dave Abrahams dabrahams at apple.com
Sat Jan 28 13:48:53 CST 2017


on Fri Jan 27 2017, Zach Waldowski <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.

The way to handle Deque is to add this requirement to Collection when
we get the language features to express it:

  protocol Collection {

    associatedtype Segments : Collection 
    where Segments.Element : Collection,
      Segments.Element.Element == Element
     = EmptyCollection<EmptyCollection<Element>>
    
    var segments: Segments? {get}
    ...
  }

  extension Collection 
  where Segments == EmptyCollection<EmptyCollection<Element>> {
    var segments: Segments? { return nil }
  }

Data structures like Deque would vend a non-nil `var segments`, and
algorithms that can benefit from segmentation would be rewritten to take
advantage of it.  The contiguous storage referenced by a Deque's
segments would be available by virtue of their conformance to
ContiguouslyStored.

>
> Cheers!
>
>   Zachary Waldowski
>
>   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[1]
>>  * Review Manager: TBD
>>  * Status: *Awaiting review*
>> *During the review process, add the following fields as needed:*
>
>>  * Decision Notes: Rationale[2], Additional Commentary[3]
>>  * Bugs: SR-NNNN[4], SR-MMMM[5]
>>  * Previous Revision: 1[6]
>>  * 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[7]
>> 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
>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> Links:
>
>   1. https://github.com/CTMacUser
>   2. https://lists.swift.org/pipermail/swift-evolution/
>   3. https://lists.swift.org/pipermail/swift-evolution/
>   4. https://bugs.swift.org/browse/SR-NNNN
>   5. https://bugs.swift.org/browse/SR-MMMM
>   6. https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md
>   7. https://lists.swift.org/pipermail/swift-evolution/
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

-- 
-Dave



More information about the swift-evolution mailing list