ValueSemantic protocol

Should we consider adding a nonmutating keyword for class functions & computed properties to let them get in on the fun?

Well, that again gets to the point that you can't really define a notion of "value" independent of some set of operations that apply to that notional value, and that it's the properties of the operation that ultimately matter. As is, you can define a value over some set of operations for every type.

Yes, and what I'vewe've done is to tie “value” and “safety” together in such a way that they allow us to reason about the behavior of operations on compositions of types whose notional value has been defined by the programmer, without repeating the same mental work for every single new operation that's written. These are the common cases that it's important to be able to work with most easily. And, I've explained how to extend the model to cover all compositions in a way that builds on the basic model.

1 Like

Yes, I generally agree with you, but for a different reason. Swift runs on a lot of non-apple platforms and that is important to consider. My rationale for agreeing with you is just that it fragments the world too much and would cut against the goal of making this safe by default. We could definitely model this, but it isn't worth it IMO.

-Chris

1 Like

That's actually the same reason; the principle of concept requirement clustering is all about avoiding fragmentation. The granularity of concepts should be enough to make the important distinctions between models, not every conceivable distinction. I don't think the category of types that are less threadsafe than Int is important enough to warrant the complexity that introducing a separate concept adds. No matter what the cost of atomic refcounting is (and there are ways to mitigate those costs), people still want to program with threads, so we simply don't often hear of types in that category playing a crucial role.

P.S. (and this is really incidental) I assumed that whatever advances Apple silicon was making in the efficiency of atomics was likely to be occurring across the industry. Without asking you to reveal any trade secrets, of course, I suppose you might have some insight into that. Care to comment?

Credit where it's due

The statement above, as I originally wrote it, hasn't been sitting right with me, so I've made a correction. I mean this: if I managed to finally write down a usable definition for ValueSemantic, it's only because of the work we did here together.

In particular

  • if @Chris_Lattner3 hadn't prodded me to start the thread, it wouldn't have happened.
  • @anandabits's post yielded the crucial insight that if value semantics was to be used for thread-safety then a large category of operations had to be considered unsafe, and that category included all the operations that expose reference semantics on types that would otherwise be considered values.
  • Everybody who engaged in the discussion provided the environment of focus and collective inquiry that I, at least, depend on for insight.

Lastly I want to thank everyone for an experience that reminded me of why I believe so strongly in collaborative open-source development.

-Dave

11 Likes

Great point, completely agreed.

I don't know anything about Apple Silicon so I'm afraid I can't say anything concrete here, but I can wildly speculate like any other industry observer :-) I would guess that they added atomic memory operations that have looser memory consistency model than the default ARM model (and certainly better than the X86 model), more aligned with the RISC-V relaxed consistency atomics. iPhones have had more efficient atomics than Intel processors for a long time AFAIK.

That said, I'm far from an expert on this and have no insider knowledge, I expect it will become more public as the hardware becomes more available.

-Chris

2 Likes

I haven't had time to engage with this thread yet, but let me just pop in to note that "semantics" is not a plural noun and cannot have its s removed. While "semantic" is indeed an adjective, it does not mean "having (these) semantics" but "related to or expressing meaning", and "value-semantic" is not in use.

7 Likes

Since Swift protocols carry semantic meaning, one could argue that having “semantic(s)” in the name at all is repetitive.

If that argument carries the day, we could call it AnyValue to be consistent with AnyObject. Given that AnyObject conformance is implicit, this is especially interesting if we go with implicit conformances to AnyValue.

4 Likes

I’ve been turning it over in my head and I disagree that ‘value-semantic’ is incorrect as an adjective. I think the best analogy is to a well-established term like ‘set-theoretic,’ meaning something like ‘related to set theory.’ Note that the term is not (or at least, not as commonly) ‘set-theoretical,’ even though the adjective from ‘theory’ is usually ‘theoretical.’ This suggests to me that ‘set-theoretic’ is best thought of as the adjective derived directly from the term ‘set theory,’ rather than being derived directly from ‘theoretical’ with ‘set’ tacked on the front. Similarly, it seems quite reasonable to derive an adjective from the noun phrase ‘value semantics’—and what else would it be, but ‘value-semantic’? Yes, the meaning is different than the simple composition of the meanings of ‘value’ and ‘semantic,’ but that’s just as ‘set-theoretic’ means something a little different than just ‘theoretical and related to sets.’

2 Likes

I also haven't followed all of this thread, but any way to make this implicit would be nice. Otherwise pretty-much every wrapper will need to add conditional conformances to indicate that they preserve the value-ness of the thing they wrap:

extension Optional: ValueSemantics where Wrapped: ValueSemantics {}
extension Slice: ValueSemantics where Base: ValueSemantics {}
... etc

LazyMapCollection and LazyFilterCollection may or may not have value semantics depending on the map/filter closure.

Setting aside existing non-salient API for the sake of discussion, is this something that should be addressed only through documentation? Or should there be a naming convention as there is with unsafe? Salience is important enough that it seems like a convention could be justified. That might make it easier to teach and would certainly make non-salience clear when reading and writing code.

FWIW, my sense is that non-salient attributes are relatively rare, and don't (in and of themselves) create a safety problem, so don't warrant a special naming convention.

Thanks, John. I was never too comfortable with the name anyway, not least for the reason cited by @Karl:

IMO Value would be a much better name, though I expect it might clash inconveniently with commonly-used associated type names, and might also cause some confusion when discussing the language-level concept of value, which is related but not identical. We could also (ab)use the name Regular, giving it a slightly different meaning from its classical definition on language-specific grounds, but SemiRegular is a closer match as a precedent.

Thanks; that reasoning is good enough for me! Since we may not be able to drop Semantic[s] from the name for practical reasons, it's enough to keep “ValueSemantic” on my table.

Hi,

I'm truly sorry I missed this thread back in November since Swift value semantics is one of my favorite topics. I thought about it back in 2016 and ended up writing a book chapter on it (in The Swift Apprentice) and also giving a talk on the topic. So it was uncanny to discover this thread, which seems to have converged on basically the same definition I settled on but with some interesting differences in emphasis.

I know it's a bit late now but I thought it might be interesting to point out some of those commonalities and differences, since I reached the same point by a different road and it can be fun to see the same view from a different angle.

First, the core conceptual definition is the same. I had settled on the view that "a type has value semantics, if the only way to modify a variable's value is through the variable itself." This seems to match the key parts of this thread's definition, that "safe code not being able to "observe the value of a except via an expression that uses a," andd that "safe code not being able to "alter the value of a" except via a explicit list of mechanisms. (As far as I can tell, all these mechanisms also involve expressions that use a.)

Another point of commonality is a kind of recursive structural test of whether a type is value semantic, based on whether its stored properties have value semantics. The thread names these as corollaries of the definition:

  • that "a let-bound instance is constant for all time"

  • that if a value type's stored properties all have value semantics, then that value type will have value semantics.

I ended up describing similar points in terms of an equivalent, constructive definition, a recipe for value semantics. You can apply this recipe either to build a value semantic type by cookbook, or else to analyze a type to determine if it has value semantics. The recipe works like this:

  1. primitive value types (like Int) are value semantic by nature

  2. a reference type is value semantic if it is immutable, which will be the case if its stored properties are let-bound and are themselves value semantic types.

  3. a value type is value semantic if its stored properties are all value semantic, or else if it handles CoW correctly (for which I describe a further recipe, and also note that VS is always defined with respect to a particular access level)

As far as I can tell, this is still a correct procedure.

What's maybe more interesting is a few differences in emphasis.

  • This thread's definition is more explicit about the "independent notional value", implied by salient properties or basis operations. I merrily dodged the issue in a footnote by saying value is what you're trying to test with Equatable. But on this topic, I noted (what I still believe is true) that value semantics can be defined without any notion of an instance at all, that this is part of the valuable simplification it provides, and that much of the confusion in this area is likely due to the historical tension between machine-oriented and mathematicaly-oriented definitions of value (e.g., C vs SML).

  • I emphasized the point that a reference type can be a value semantic type, as a consequence of the natural definition, and argued that "value type"-vs-"reference type" is an implementation detail, while "value semantic"-vs-not is essentially a key part of a type's interface.

  • I also offered an equivalent adversarial definition in terms of "the mutation game," an imagined fight between Victor the Valucist and a Salazar the SideEffector, to test if a type is immune from side effects. This strikes me as useful for intuition and teaching.

  • The definition in this thread states that part of what is meant by value semantics is that "concurrent access to the values of distinct variables of type X cannot cause a data race," and that concurrency properties are emphasized more generally. I can see that needs to be stated explicitly, since it doesn't follow automatically from other points.

Anyway, I hope it is not too wildly self-indulgent for me to retread this, as it's all old news now! But if anyone is still curious, you can still see my old talk online.

5 Likes

/cc @saeta @shabalin @Alvae @dan-zheng

Starting from the end…

Not at all; very happy to see more interest in this.

It's not quite equivalent, I think…

What's maybe more interesting is a few differences in emphasis.

  • This thread's definition is more explicit about the " independent notional value ",

I think we need the idea of notional value to define value semantics rigorously. There are a few holes in your definition, IMO, that I intended to close by introducing this idea:
• There are no primitive types in Swift, so you don't really have a meaningful basis case. Is AnyObject a primitive type?
• Operations on a value type can access mutable globals, and can present the values of those globals as though they are part of its value. I could make an empty struct with exactly the same API surface as Int, and it would not have value semantics in the sense we mean the term.

In the general case, whether a type has value semantics is not a property that can be derived from the code that defines the type; it's a property of the programming model presented by the type to its clients.

value semantics can be defined without any notion of an instance at all

Sorry, I don't know what point you mean to make here; the definition I offered does not mention the word “instance.”

I collaborated on a research paper about this recently, and I can say we studiously avoided that word because its meaning is fuzzy at best. That's one of the reasons my definition mentions variables.

4 Likes

I think what @algal describes here covers exactly value semantics.

With Value Semantics, the mapping between variable and value becomes n to 1 such that a variable is nothing more than a local constant representing some value, mutating that value would rebind the variable, we can even state here that we create a new variable with the same name.

Which is however different for Non Value Semantics, where we have a m to n relationship between value and variable, because a variable isn't anymore an alias to a value. Instead, it is an alias to some resource holding an instance which represents a value.

The resource is some abstract location able to represent values of some type with representatives/instances, it would mostly map to a memory location in the backend.

The instance/representative is either sides some kind of value which can be mapped to a value inside the type system, it would be a binary sequence in most backends.

I think projecting value semantics to types is a failure, if we were allowed to pass structs by plain reference (not inout) then we would undermine the concept of "Value Semantics" for Types.

A type shouldn't provide value semantics, instead variables should do that with appropriate annotations/keywords.

Unfortunately, one cannot describe value semantics solely in terms of whether or not one is able to create an alias onto some memory location. Values of types having value semantics compose in a way other values do not.

When we think about aggregates, value semantics must be understood as a property of the programming model, because one cannot determine whether a property is a part of the aggregate, or a mere association. In a typed language, it makes sense that this property be associated with types, as types are the abstraction we use to talk about values, statically.

Here is an example:

class Vec2 { var x, y: Int }
struct Rect { var width, height: Vec2 }

Whether or not width and height are part of Rect is not clear from the code alone. One can imagine these properties are intended to be shared between different shapes (i.e., they represent an observer/observed relationship), or that they are truly part of the a (i.e., they represent a whole/part relationship), yet their type must be implemented as a class for some other reason.

Hence, whether or not the value can be represented as a constant is an orthogonal problem. Clearly, let r = Rect(...) creates a constant r that does not even require to be stored in an abstract location, yet r does not have value semantics.

It is convenient that Swift can rule out this case by implementing Vec2 as a struct rather than a class (because Int also has value semantics), and probably the reason why the inductive definition offered by @algal seems so appealing. Unfortunately, this approach does not cover all cases, as mentioned by @dabrahams.

I would grant, however, that the inductive definition does work if base cases are defined asana types having value semantics, rather than attempting to identify a specific set of "primitive" types.

I do not think it would, as long as one can guarantee that uniqueness of mutable access is preserved, and this can be achieved in different ways. In Swift, we use inout, in Rust, we use borrowing. In both case, the mechanisms preserve value independence.

I think we should separate the definition of "value semantics" from the language features that can be used to implement it.

I also think that the mere ability of a language to break the invariants of a given abstraction does not mean the abstraction is moot. One can break value independence in Swift, yet we happily claim that Int has value semantics.

var i = 99
func f(x: inout Int) { x = i + 1 }
f(x: &i) // Oops ;)

Unfortunately, keywords and annotations are typically insufficient to describe value semantics. At the very least, they are in Swift. Here are two examples:

var i = 99
struct Foo {
  var p: Int { i }
}

struct Bar {
  private var p: NSArray
}

Foo does not have value semantics, because variables of type Foo can be changed externally. Yet this type definition does not feature the class keyword.

Bar may or may not have value semantics, depending on the intended meaning of the relationship with the value of the property p (independently of the ability to break its intended semantics). Note that private is insufficient to prescribes that Bar has value semantics, because p's value might be aliased when Bar is instantiated.

Incidentally, I think these observations partially explain why defining a ValueSemantic protocol is rather challenging.