This is a great question. Here are some strategies I’ve used for making frame conditions easier to express:
— Rather than expressing an update to a relation as
x.r’ = e and all x’: X - x | x.r’ = x.r`
use relational override and write
r’ = r ++ x -> e
— Organize events into a hierarchy using signature extension, and associate frame conditions with larger event classes. For example, in the hotel model in my book (p. 204), there’s a class of events called RoomKeyEvent which has a frame condition associated with it saying there is no change at the front desk.
— Separate frame conditions out into predicates that can be reused. The same example in my book uses a predicate noRoomChangeExcept [rs: set Room] which constrains there to be no change except possibly for the set of rooms rs. Such a predicate can also take a relation as an argument, although Alloy’s type system can get in the way a bit here (but can resolve overloaded predicate names with relations of different types).
— Use Reiter style frame conditions (my Alloy book, p203) in which you write a constraint that says that if a particular relation was modified, a certain event must have happened: eg, this constraint says that if the occupant of a room changed, then a checkin or a checkout must have happened:
occupant.t != occupant.t’ => e in Checkin + Checkout
— Not sure of the status of the implementation, but Alloy has a minimally documented metafacility (see http://alloytools.org/quickguide/meta.html) which allows you to quantify over fields, so you can say something like
all f: $Field | x.f = x.f’
Note that most of these strategies were developed prior to Alloy 6, which has made it more reasonable to use only predicates and not explicit event objects to define state transitions. Hopefully they can all be adopted in A6, maybe even in a better form, but I haven’t had a chance to look into that.
On a methodological note: Alloy’s declarative style, in which behavior is specified just by constraints, is what makes it powerful in many ways. Rather than saying everything at once, as you’d have to do in a program, you can say as little as possible and build a model up incrementally, adding more constraints to rule behaviors out, and discovering what is really needed to make a requirement true. This is also what lets you compose by conjunction, and makes it possible to model abstraction functions. It lets you define state components in terms of other state components, and also (most basically) define behavior by observation rather than construction (for example, saying that the value of a nonce is any value not used before).
But the flip side of this merit is frame conditions and the annoyance they cause. If I was designing Alloy again, especially given the advances of Alloy 6 which relies on embedding a particular model of dynamics (which basic Alloy does not do), I would include statement forms that include implicit frame conditions. I use such statements in modeling concepts in my recent book (Essence of Software, p227), writing things like
x.p += e
p’ = p + (x -> e)
I think there’s a great project to do in extending Alloy with such syntax!