[swift-evolution] Should we rename "class" when referring to protocol conformance?
Dave Abrahams
dabrahams at apple.com
Sun May 22 12:04:19 CDT 2016
on Mon May 16 2016, Matthew Johnson <swift-evolution at swift.org> wrote:
>> On May 15, 2016, at 2:01 PM, Dave Abrahams
>> <dabrahams at apple.com> wrote:
>>
>>
>> on Fri May 13 2016, Matthew Johnson <matthew-AT-anandabits.com
>> <http://matthew-at-anandabits.com/>> wrote:
>>
>
>>> Sent from my iPad
>>>
>>>> On May 13, 2016, at 9:12 AM, Dave Abrahams <dabrahams at apple.com> wrote:
>>>>
>>>>
>>>>> on Mon May 09 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:
>>>>>
>>>>> My claim is that substituting the constraint of “it has value
>>>>> semantics,” while presumably looser than the PureValue constraint, would
>>>>> not compromise the correctness of your view controller, so not only is
>>>>> the meaning of PureValue hard to define, but it doesn't buy you
>>>>> anything. If you want to refute that, just show me the code.
>>>>>
>>>>> This is not an algorithmic use but is still perfectly valid IMO.
>>>>>
>>>>> If the properties of PureValue matter to your view controller, there's
>>>>> an algorithm somewhere that depends on those properties for its
>>>>> correctness.
>>>>>
>>>>> In many cases it may just be view configuration that depends on those
>>>>> properties. I suppose you can call view configuration code an
>>>>> algorithm but I think that would fall outside of common usage.
>>>> It's an algorithm, or if the configuration is declarative, there's
>>>> an algorithm that manipulates it. That said, I still don't have a
>>>> concrete example of how view configuration can depend on these
>>>> properties.
>>> The algorithm might just be "copy x bit of data to y view
>>> property, etc". That is so trivial that it feels like a stretch to
>>> call it an algorithm.
>>
>> Algorithms can be trivial.
>
> Fair enough. Although in most contexts people don’t use the word when
> discussing the trivial.
Yes, quite a shame, that.
>>> That "algorithm" doesn't depend on this property because it
>>> executes at a single point in time. However, a view controller
>>> might depend on that property in order to render properly across
>>> time (for example, configuring cells as they scroll on and off
>>> screen).
>> The example is too abstract for me to understand.
>>
>> Let me put this differently: I recognize that your concept of
>> “PureValue” may be a *sufficient* condition for some generic
>> algorithm/component to work, but it is never a *necessary*
>> condition, because genericity (or use of a superclass or protocol
>> type) erases details of the actual types involved from the point of
>> view of the algorithm/component. It doesn't matter if your type
>> contains a class reference if it has value semantic properties. My
>> claim is that PureValue is an overly-restrictive constraint that
>> makes many things less useful than they should be.
>
> In many cases this is true - you don’t need more than value semantics
> as you define it. However it is not at all true that PureValue is
> never necessary for the correctness of code. I’m going to provide an
> example to the contrary below.
>
>>
>>> This property allows us to separate values from non-local mutation
>>> and make such separation a requirement. Rather than observing
>>> mutations of objects with KVO, etc we might prefer to observe
>>> something that provides a new aggregate value instead, while
>>> requiring the entire aggregate value itself to be (observably)
>>> immutable at all times (because we stored it with a let property
>>> locally). This can make it easier to reason about correct behavior
>>> of your code. But it doesn't work unless all visible parts of the
>>> aggregate are immutable.
>>>
>>> If you're not familiar with Elm, Redux, etc it might be worth
>>> taking a look.
>> That's a pretty broad link. At which parts do you think I should
>> look?
>
> The piece that matters here is state management. The core concept is
> to tightly control how mutations happen. It is modeled in terms of
> state type T, an initial value t, an action type A (instances of which
> are mutation commands, as in the command pattern), and a reducer
> function (T, A) -> T which produces a new state.
>
> Here’s a toy implementation that is somewhat simplistic but captures
> the essence of the concept:
>
> class Store<State, Action> {
> typealias Reducer = (State, Action) -> State
>
> var stateHistory: [State]
> let reducer: Reducer
>
> init(initialState: State, reducer: Reducer) {
> stateHistory = [initialState]
> self.reducer = reducer
> }
>
> func applyAction(action: Action) {
> let newState = reducer(stateHistory.last!, action)
> stateHistory.append(newState)
> }
>
> var currentState: State {
> return stateHistory.last!
> }
>
> var canUndo: Bool {
> return stateHistory.count > 1
> }
>
> func undo() {
> if canUndo {
> stateHistory.popLast()
> }
> }
> }
>
> This design relies on State being a PureValue. The whole idea is that
> the only way any path of observation rooted at currentState can change
> is when the reducer returns a new state when it is called by
> `applyAction`. That guarantee
I'm sorry, I can't understand what guarantee you're describing here.
Can you describe it in terms of preconditions, postconditions, and
invariants?
Just looking at the code, it seems to me that the only actual
requirement for sane results here is that the result (and effects) of
the `reducer` function depends only on the values of its arguments.
This is a highly precedented kind of requirement. For example, you
don't expect sort() to produce meaningful results if the comparison
function's result changes based on something other than the values of
the elements being compared.
> cannot be provided by value semantics alone under your definition of
> value semantics. Further, each state in the history is intended to be
> a static snapshot of the “currentState” state at a specific point in
> time. All states should be logically independent from each other and
> from anything anywhere else in the program. This cannot be guaranteed
> under your definition of value semantics.
That is exactly my definition of value semantics (modulo aggregation; a
value that is composed of other values obviously is dependent on the
values it's composed of). But distinct instances of types with value
semantics have values that are logically independent.
> If we allow State to be Array<MyMutableReferenceType> which has value
> semantics under your definition it is clear that we should have no
> expectation that the desired properties are preserved.
> The Store class is fundamentally broken if it can be used with State
> types that are not pure values.
It depends on the semantic requirements placed on `reducer`. If you
insist that `reducer` should be able to do anything at all, then clearly
you need to constrain `State` in ways that make this component less
useful than it might otherwise be. But even then, you can't allow
`reducer` to *whatever* it wants because it could circle back and modify
the `Store` instance.
> I’m not sure why it didn’t occur to me sooner, but this strategy for
> managing app state is very similar in nature to what Sean Parent
> discusses in his value semantics talk
> (https://www.youtube.com/watch?v=_BpMYeUFXv8
> <https://www.youtube.com/watch?v=_BpMYeUFXv8> and related
> https://www.youtube.com/watch?v=bIhUE5uUFOA
> <https://www.youtube.com/watch?v=bIhUE5uUFOA>).
>
> Sean discusses using value semantics to model applications state (he
> calls it document). His examples don’t use reified actions and
> reducer functions so it is a bit different but it relies on the same
> pure value semantics. The demo he gives in the talk is a toy example
> modeled on the design Photoshop uses to implement its history feature.
> This design relies on each document in the history being an aggregate
> which is a PureValue. This is not an academic discussion, but on with
> real world practical utility.
I'm very familiar with this talk; it was a major inspiration for my
presentation at WWDC last year. I'll freely admit I get most of my good
ideas from Sean ;-)
> Sean says “value semantics is similar to functional programming,
> except objects still have addresses and in-situ operations… You're
> trying to maintain the ability to locally reason about your code but
> you're not denying the fact that the machine has memory and you can do
> in-situ operations on it”.
>
> Towards the end he quotes from a discussion he had with John Backus
> (inventor of FP). John said: “it always annoyed me that FP and the no
> side effect way of programming had become a religion. With FP I was
> trying to come up with a mathematically rigorous way to program and I
> understood the complexities of having side effects. I always knew we
> had to deal with side effects, I just wanted a structured way to do
> it.” John agreed that Sean’s approach to value semantics provides
> such a structure. Allowing shared mutable references throws away that
> structured approach. (As an aside, this is the most exciting thing
> about value semantics IMO - it provides a structured approach to side
> effects, allowing local reasoning about code).
>
> Sean gives an example of how references break the ability to reason
> locally where he has two shared_ptrs that point to the same object:
>
> "If you look at it in terms of just the individual types you kind of
> do have value semantics. When I copy a shared pointer it copies the
> pointer with value semantic operations... The problem is the
> references. When I'm looking at a shared_ptr I'm looking at it as if
> I have the object.
This is the key phrase; it is about the implied programming model for
shared_ptr, which is a cultural phenomenon, not an absolute truth. Yes,
when people get a reference to a class instance in Swift, they normally
don't even think about what's stored in the instance as being distinct
from the value of the reference, and indeed the language syntax is more
hostile to making that distinction than the syntax of C++. Defining the
value of a reference to be the address it points at (unless the instance
is immutable) allows everything to fall back into place, logically
speaking.
Whether programmers can learn to understand the world this way is
certainly debatable, but it's pretty clear to me that leaving “the value
of a reference” undefined is untenable, and defining it in any way that
doesn't result in a reference having value semantics would make
describing algorithm semantics almost impossible. So what's the
alternative?
> So really what I have is two objects that intersect. So really my
> object in the program is this whole connected mess. At any particular
> point in code I have difficulty reasoning about the whole thing. The
> shared structure breaks our ability to reason locally about code."
>
> Sean makes an important distinction between looking at individual
> types and looking at the aggregate as a whole. It is very important
> to him that the entire aggregate be logically independent as this
> facilitates local reasoning. This is exactly what I have been calling
> a pure value. Pure value never allows any intersection to be observed
Yes, but: you can only measure an intersection of two values if *you
stay within the boundaries of those values*. A value type can contain a
reference to a shared cache as an incidental part, and this reference
can even be observable, as long as it is clearly distinguished as *not*
being within the boundaries of the value. One consequence of that is
that the results of equality comparison would never depend on the state
of the cache.
> (immutable intersections are allowed because they cannot be observed,
> which Sean alludes to in passing). Incidentally, it is pretty clear
> from the talk that immutable intersection is heavily used in Photoshop
> history in order to keep memory use reasonable. This falls into the
> category of persistent data structures.
>
> My impression is that Sean’s definition of "value semantics” excludes
> “intersecting objects” (where the intersection is mutable) and is
> aligned with John’s “full value semantics” and the notion of “pure
> value” we have been discussing.
I think I discussed this with Sean a week ago...
--
-Dave
More information about the swift-evolution
mailing list