You make me glad :-)
FWIW, the closure technique is also used by GRDB's ValueObservation. It is required to evaluate the closure in order to discover the tracked values. But that's totally OK, since some people (me included) expect an initial value anyway :-)
Since I'm sharing experience, here's a gotcha. When the user tracks a value built from a non-constant set of observable properties, it is important not to reify the set of observed properties for the whole duration of the observation.
For example, let's track the result of this function, which does not always access the same properties:
func value(_ state: MyState) -> String {
if state.someBool {
return state.foo
} else {
return state.bar
}
}
In the case of ObservationTracking.withTracking
, there's no problem, because this method is only supposed to notify the first change:
let state: MyState
ObservationTracking.withTracking {
print(value(state))
} onChange: { ⊠}
But for a closure-based version of value(for:)
, it is different. The set of tracked properties needs to be refreshed on each closure evaluation, so that changing someBool
can alternate between foo
and bar
tracking:
let state: MyState
let sequence = state.track { value($0) }
This reevaluation of the set of tracked properties takes a performance toll, and it makes it impractical to reify anything at the type level 
But it fosters a correct use of the api. (w.r.t. composition and invariants, as discussed above).
For interested people, how GRDB (humbly) handles changing vs. constant set of tracked values
In order to remove the performance toll of reevaluating the tracked "region", I had to make ValueObservation api a little more complex.
The attractive case is for people who do not want to think or optimize:
// Reevaluates the tracked region on each evaluation
ValueObservation.tracking { db in
if /* fetch some bool from db */ {
return /* fetch some string from db */
} else {
return /* fetch some other string from db */
}
}
Demanding users may opt in for an optimized observation that performs a single evaluation of the tracked region, at the beginning of the observation:
// User opts in for optimized observation when they
// know what they are doing:
ValueObservation.trackingConstantRegion { db in
// ~~~~~~~~~~~~~~~~~~~~~~
// With this api, it is important to always access
// the same db region, or some changes will be missed.
let someBool = /* fetch some bool from db */
let a = /* fetch some string from db */
let b = /* fetch some other string from db */
return someBool ? a : b
}
// Variant that performs less fetches:
ValueObservation.trackingConstantRegion { db -> String in
try db.registerAccess(to: /* where the first string comes from */)
try db.registerAccess(to: /* where the second string comes from */)
if /* fetch some bool from db */ { // automatically registers the bool region
return /* fetch some string from db */
} else {
return /* fetch some other string from db */
}
}
The optimization is real: in the non-optimized case, GRDB must fetch new values from an exclusive write access. Only optimized observations can leave the exclusive write access (so that other writes can start) and fetch concurrently.
I was so proud of the optimized observation that I initially made it the default.
I quickly reverted this decision
Of course requiring users to think a lot wasn't the best option. Principle of least astonishment and correctness first, but optimization is possible for experts.