[swift-evolution] access control proposal
Ilya Belenkiy
ilya.belenkiy at gmail.com
Mon Dec 14 06:25:11 CST 2015
If you were writing a book, would you want to put every paragraph into a
separate file?
On Mon, Dec 14, 2015 at 2:49 AM ilya via swift-evolution <
swift-evolution at swift.org> wrote:
> > I believe that *most*—certainly not all, but most—uses of `local` are
> like this. `local` would improve the code's safety, but some sort of
> refactoring would be even better. Because of that, I don’t think `local` is
> as valuable as you think it is.
>
> +1.
>
> Let's try to promote existing access mechanisms, such as separating code
> into a number of files, first, before deciding that we need new keywords.
>
>
> On Mon, Dec 14, 2015 at 4:53 AM, Brent Royal-Gordon via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>> >> But it’s not zero-cost. It’s another thing to learn, another thing to
>> think about, another way you have to mentally analyze your code.
>> >
>> > I meant zero performance cost. Of course all features have "cost" if
>> we mean "cognitive overhead". Type safety for example has a huge cognitive
>> overhead. Think back to the days of "Bool is not an NSString". But the
>> benefit of typesafety is large.
>> >
>> > In this case, the cognitive overhead is small, and so is the benefit.
>> But I think the value-per-unit-cost is similar. In both cases the compiler
>> helps you not do something very very bad, that is hard to debug in ObjC.
>>
>> And I’m saying that I think the benefit is even smaller than you think it
>> is, because you can usually get the same benefits by other means, and the
>> resulting code will be *even safer* than `local` would give you. Again,
>> consider the “value protected by a queue” case. Using `local` here limits
>> the amount of code which could contain an access-without-synchronization
>> bug, but `Synchronized` *completely eliminates* that class of bugs.
>> Synchronized is a *better*, *safer* solution than `local`.
>>
>> I believe that *most*—certainly not all, but most—uses of `local` are
>> like this. `local` would improve the code's safety, but some sort of
>> refactoring would be even better. Because of that, I don’t think `local` is
>> as valuable as you think it is.
>>
>> >> many people, when they do that, find this idea wanting.
>> >
>> > Who? You? Then build an argument around that. I don't know who "many
>> people" are or what their justification is.
>>
>> I’m sorry, I don’t mean to make it sound like I’m speaking for some big,
>> ill-defined posse. I just mean that different people will draw the line on
>> the necessary cost-to-benefit in different places, and for some, this
>> feature will fall on the wrong side of the line. People who don’t like this
>> feature don’t misunderstand it; they just have a different subjective
>> assessment of its value.
>>
>> > My justification is essentially that A) something like Synchronized is
>> a problem nearly everybody has and B) the difficulty of defining a
>> class-based solution in an optimal way.
>> >
>> > On B, we seem to agree:
>> >
>> >> it might be difficult to construct a Synchronized instance correctly.
>> >
>> > So I can only conclude you disagree about A. However, I think my A is
>> much stronger than is strictly necessary to demand a language feature.
>> There are plenty of language features that not everyone uses, so the fact
>> that you don't have a need for it (or even "many people") is not really a
>> counterargument I am able to understand.
>>
>> As far as a standard Synchronized is concerned, I agree with you that
>> it’s a good idea. I am simply *worried* that we may have trouble coming up
>> with a design that’s flexible enough to accommodate multiple
>> synchronization methods, but still makes it easy to create a
>> properly-configured Synchronized object. For example, something like this
>> would be so complicated to configure that it would virtually defeat the
>> purpose of having a Synchronized type:
>>
>> class Synchronized<Value> {
>> init(value: Value, mutableRunner: (Void -> Void) -> Void,
>> immutableRunner: (Void -> Void) -> Void) { … }
>> …
>> }
>>
>> So I’m going to think out loud about this for a minute. All code was
>> written in Mail.app and is untested.
>>
>> I suppose we start with a protocol. The ideal interface for Synchronized
>> would look something like this:
>>
>> protocol SynchronizedType: class {
>> typealias Value
>> func withMutableValue<R>(mutator: (inout Value) throws ->
>> R) rethrows -> R
>> func withValue<R>(accessor: Value throws -> R) rethrows
>> -> R
>> }
>>
>> You could obviously write separate types like:
>>
>> class QueueSynchronized<Value>: SynchronizedType {
>> private var value: Value
>> private let queue: dispatch_queue_t
>>
>> init(value: Value, queue: dispatch_queue_t =
>> dispatch_queue_create(“QueueSynchronized”, DISPATCH_QUEUE_CONCURRENT)) {
>> self.value = value
>> self.queue = queue
>> }
>>
>> func withMutableValue<R>(@noescape mutator: (inout Value)
>> throws -> R) rethrows -> R {
>> var ret: R?
>> var blockError: NSError?
>> dispatch_barrier_sync(queue) {
>> do {
>> ret = try mutator(&value)
>> }
>> catch {
>> blockError = error
>> }
>> }
>> if let error = blockError {
>> throw error
>> }
>> return ret!
>> }
>>
>> func withValue<R>(@noescape accessor: Value throws -> R)
>> rethrows -> R {
>> var ret: R?
>> var blockError: NSError?
>> dispatch_sync(queue) {
>> do {
>> ret = try accessor(value)
>> }
>> catch {
>> blockError = error
>> }
>> }
>> if let error = blockError {
>> throw error
>> }
>> return ret!
>> }
>> }
>>
>> and:
>>
>> class NSLockSynchronized<Value>: SynchronizedType {
>> private var value: Value
>> private var lock: NSLock
>>
>> init(value: Value, lock: NSLock = NSLock()) {
>> self.value = value
>> self.lock = lock
>> }
>>
>> func withMutableValue<R>(@noescape mutator: (inout Value)
>> throws -> R) rethrows -> R {
>> lock.lock()
>> defer { lock.unlock() }
>>
>> return try mutator(&value)
>> }
>>
>> func withValue<R>(@noescape accessor: Value throws -> R)
>> rethrows -> R {
>> // XXX I don’t know how to get concurrent reads
>> with Cocoa locks.
>> lock.lock()
>> defer { lock.unlock() }
>>
>> return try accessor(value)
>> }
>> }
>>
>> But that’s not a very satisfying design—so much boilerplate. Maybe we
>> make the thing you’re synchronizing *on* be the protocol?
>>
>> protocol SynchronizerType: class {
>> func synchronizeForReading(@noescape accessor: Void ->
>> Void)
>> func synchronizeForWriting(@noescape mutator: Void ->
>> Void)
>> }
>>
>> final class Synchronized<Value> {
>> private var value = Value
>> private let synchronizer: SynchronizerType
>>
>> init(value: Value, on synchronizer: SynchronizerType) {
>> self.value = value
>> self.synchronizer = synchronizer
>> }
>>
>> func withMutableValue<R>(@noescape mutator: (inout Value)
>> throws -> R) rethrows -> R {
>> var ret: R?
>> var blockError: NSError?
>> synchronizer.synchronizeForWriting {
>> do {
>> ret = try mutator(&value)
>> }
>> catch {
>> blockError = error
>> }
>> }
>> if let error = blockError {
>> throw error
>> }
>> return ret!
>> }
>>
>> func withValue<R>(@noescape accessor: Value throws -> R)
>> rethrows -> R {
>> var ret: R?
>> var blockError: NSError?
>> synchronizer.synchronizeForReading {
>> do {
>> ret = try accessor(value)
>> }
>> catch {
>> blockError = error
>> }
>> }
>> if let error = blockError {
>> throw error
>> }
>> return ret!
>> }
>> }
>>
>> extension dispatch_queue: SynchronizerType {
>> func synchronizeForReading(@noescape accessor: Void ->
>> Void) {
>> dispatch_sync(self, accessor)
>> }
>>
>> func synchronizeForWriting(@noescape mutator: Void ->
>> Void) {
>> dispatch_barrier_sync(self, mutator)
>> }
>> }
>>
>> extension NSLock: SynchronizerType {
>> func synchronizeForReading(@noescape accessor: Void ->
>> Void) {
>> // XXX I don’t know how to get concurrent reads
>> with Cocoa locks.
>> lock()
>> accessor()
>> unlock()
>> }
>>
>> func synchronizeForWriting(@noescape mutator: Void ->
>> Void) {
>> lock()
>> mutator()
>> unlock()
>> }
>> }
>>
>> Huh. That’s…actually pretty clean. And I suppose if you want it to
>> default to using a dispatch queue, that’s just a default value on
>> Synchronized.init(value:on:), or a new init(value:) added in an extension
>> in LibDispatch. I thought this would be a lot hairier.
>>
>> So, yeah, I guess I like the idea of a standard Synchronized. A
>> well-designed version (one with something like SynchronizerType) can work
>> with multiple locking mechanisms, without resorting to something
>> error-prone like passing in closures. +1 for that.
>>
>> --
>> Brent Royal-Gordon
>> Architechies
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> 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/20151214/93473192/attachment.html>
More information about the swift-evolution
mailing list