This seems similar to replicating substructural type systems like linear or affine types via interfaces.
I can't remember who gave this talk, but a prime example is a `File` type. A `File` type shouldn't have a Read() method until the file itself has opened. And calling `Close()` on the File should prevent further reads. Substructural typing - and this pattern - allows you to refine types based off of the type's instance state.
There's a ton possible here but I haven't really kept up to date with type systems in the last 5 years. Stuff like this has probably advanced a ton!
Wouldn't the instance state here be "was constructed"? That is: you should always be able to call `Read` because the constructor opened the file, and you shouldn't have to call `Close` anyway because the destructor did.
I thought this was going to be a full state machine, where each state has a different interface, but it looks like the Resolved state is just the result of Resolver.Execute(). Wouldn't it be preferable to return the result in a new object, rather than reusing the same object?
This reminds me of the Make Illegal States Unrepresentable[0] idea, where something will return a ‘ResolvedExecutor’ type to guarantee that you’re only working with something in the state you want. Go makes this a little clunky to do, though. I suppose you could use Generics to implement a Preresolved and Resolved struct.
You start with a Resolver, call Resolver.Execute, and up with a Resolved (which happens to be the same object, but that's really an implementation detail). But the type system doesn't prevent you making further calls to Resolver -- that's one of the illegal uses we'd like to prevent, if I understand the example.
It might work in a language with linear types or some concept of ownership. Execute() would take ownership of the Resolver and prevent it being called again.
Maybe I misunderstand the example, but in what way is that "the whole point"? You can implement it that way, and maybe save a tiny bit of memory (but Go has a great garbage collector so it doesn't seem very significant). But it could also cost memory if the pre-resolved object has state that isn't needed after Execute() is called.
The given example looks like a Builder pattern. Although that name smells strongly of Java, so Go users might prefer to avoid it. :)
For me a "builder pattern" is also something like this with immutable data:
built = empty().configA(a).configB(b). ... .configZ(z)
where each
config?(this : T, k : K) -> T
For me the defining feature of a builder is the (possibility of a) gradual "configuration" (or whatever) of the data structure being "built". A state change is something else in my opinion, but of course, the difference between a "state change" and a "configuration" (which does change state) isn't a clear one, so if for you that is still a builder, that's fine for me too ;).
No, I think it is the same thing. Typically¹ with a Builder there's one final method that builds and returns the final object (which has a different type).
¹ Edit to add: well, it's a pattern I've seen a lot, anyway. I think it's preferable to mutating the final object in place because it "makes illegal states unrepresentable" as others in this discussion have mentioned.
If you want to use this pattern, you'll probably end up with a lot of duplicated code e.g. for SQL query builders. I could imagine that Rust makes this kind of pattern easier due to traits and macros. A lot of languages do not have these features.
And then of course Rust people care about correctness more in general.
I can't remember who gave this talk, but a prime example is a `File` type. A `File` type shouldn't have a Read() method until the file itself has opened. And calling `Close()` on the File should prevent further reads. Substructural typing - and this pattern - allows you to refine types based off of the type's instance state.
There's a ton possible here but I haven't really kept up to date with type systems in the last 5 years. Stuff like this has probably advanced a ton!