[swift-evolution] access control proposal

Ilya Belenkiy ilya.belenkiy at gmail.com
Mon Dec 14 06:22:47 CST 2015


"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."

I am not sure that this is true. Do you really see no value in completely
hiding implementation details of an API and making it enforceable by the
compiler?

On Sun, Dec 13, 2015 at 8:53 PM 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151214/f4c7a062/attachment.html>


More information about the swift-evolution mailing list