A concrete example is for managing the active item in a list. Instead of storing the active item as an index into the vector like this:
struct List<T> {
items: Vec<T>,
active: usize,
}
...which two the glaring impossible states. The vector can be empty, or the index can be outside the vector. Each time the active item is desired, we must check the index against the current state of the list.Instead, we can use the zipper concept so we always have a concrete active item:
struct List<T> {
prev: Vec<T>,
active: T,
next: Vec<T>,
}
Switching to a different active item requires some logic internal to the data structure, but accessing the active item always results in a concrete instance with no additional checks required.[0]: https://sporto.github.io/elm-patterns/basic/impossible-state...
self.prev.iter()
.chain(iter::once(self.active))
.chain(self.next)
I'm not sure what you mean by including active in another position, but see my sibling comment that makes the active element of a different type, for another wrinkle on this thing.It's the API that makes something impossible to misuse, and they could offer the same API like List.create(x: T, xs: T[]), but the first one is simpler.
struct List<T, A> {
prev: Vec<T>,
active: A,
next: Vec<T>,
}
This could be used for some active type that has ephemeral cache information or state associated with it (view state in a GUI app, for instance). The inactive type may be hydrated and converted to active, and the active type can be archived into an inactive type.https://journals.sagepub.com/doi/abs/10.3233/FUN-2005-651-20...
https://github.com/xdavidliu/fun-problems/blob/main/zipper-t...
https://web.archive.org/web/20160328032556/http://www.goodma...