2 ms·
Their example of pushing it into the type system is nice but requires a language with a fairly powerful type system. Once simple way to do this in basically any
by kevincox 5y ago
Their example of pushing it into the type system is nice but requires a language with a fairly powerful type system. Once simple way to do this in basically any statically typed language is just requiring a "Run Token" for any bit of code that does something "for real". This method works well if you want the output to be similar or identical for dry run and actual run.
struct RunToken;
fn do_the_thing(_: RunToken, ...);
The general approach works for just about any statically typed languages (although null is the enemy).
You can make it a bit more tedious to generate that RunToken so that a `do_the_think(RunToken, ...)` looks more natural. But the idea is simply that if the only place you create a RunToken is when parsing the --no-dry-run flag you can't accidentally do_the_thing when you move the code out of the `if is_dry_run` block without noticing.
Of course this is a simple way to get fairly reliable dry run. I do agree that having seperate plan and apply steps is approach is the best when you can do it.
- AgentME 5y agoThis is almost like one step toward an object-capability system. In an object-capability language, the main function would receive capability objects, like a reference to a module for accessing the filesystem, and functions could only use those capabilities if they were passed a reference to them. You could write your main function so it takes the filesystem module, creates a wrapper around it that only exposes read-only functionality, passes that read-only wrapper around to most functions that build up some kind of plan, and then the main function uses the raw unrestricted filesystem module to execute the plan at the end depending on the dry-run flag. You can then be easily sure that code doesn't do any writes to the filesystem if the unrestricted filesystem module isn't passed to it.