3 ms·
The idea of hoisting precondition ifs into the caller is terible! Sure there are special cases where it's a good idea (if nothing else it skips a function call
by gumby 3y ago
The idea of hoisting precondition ifs into the caller is terible! Sure there are special cases where it's a good idea (if nothing else it skips a function call) but in the common case you don't want to this.
In a library you want to check preconditions at the external boundary so the actual implementation can proceed knowing there are no dangling pointers, or negative numbers, or whatever the internal assumptions may be. Depending on the caller to do the check defeats the purpose.
Also in many cases you would need to violate encapsulation/abstraction. Consider a stupid case: `bool cache_this (T obj)`. Let the cache manager itself check to see if the object is already there as it can probably touch the object fewer times.
I agree on the `for` case but it's so trivial the article barely talks about it. Basically it's the same as the encapsulation case above.
- deleted 3y ago[deleted]
- LegionMammal978 3y ago> In a library you want to check preconditions at the external boundary so the actual implementation can proceed knowing there are no dangling pointers, or negative numbers, or whatever the internal assumptions may be. Depending on the caller to do the check defeats the purpose. I think the idea is to instead address this with a type-safe interface, designed so that the external boundary physically cannot receive invalid input. The caller would then be responsible for its own if statements when constructing the input types from possibly-invalid raw values. > Also in many cases you would need to violate encapsulation/abstraction. Consider a stupid case: `bool cache_this (T obj)`. Let the cache manager itself check to see if the object is already there as it can probably touch the object fewer times. I don't see the suggestion as encouraging such a thing: the "cache_this" check should only ever be performed when it's known for certain that the user wants to access the cached object, so the entry point of the cache abstraction acts as a kind of boundary that the if statement depends on. And the if statement clearly shouldn't be pushed above its own dependency.
- hedora 3y agoIn cases where that doesn't make sense: let f = Some(get_a_u16()); foo(f); ... func foo(f: u16) -> u16 { match f { None => 0, Some(f) => f * 1234 } } I'd expect any reasonable compiler to include enough link time optimization and dead code elimination to compile the whole mess down to a single multiply instruction.
- LegionMammal978 3y agoI don't see what you're tying to show with your example? The typical case is, you start with a raw type in the caller (u16), and then you create a derived type (Option<MultipliedU16>) as a result of validation. Also, you'd ideally want to handle overflow errors in the multiplication, especially with something as small as a u16. (And in case it helps anyone, for a trivial function to be inlined across crates in Rust, either the function must be marked with #[inline], or a special LTO option must be explicitly set in the Cargo profile. So often it's a good idea to mark all public trivial utility functions in a crate with #[inline].)
- hedora 3y agoIn my example, passing in none returns a value, not an error, so the intent is that it makes sense for that API to handle the None case in normal operation. However, in situations where the compiler can infer the argument is never None, it can remove the branch. (Yes, that function should probably handle overflow explicitly, but that wasn't the point.)
- gumby 3y ago> I think the idea is to instead address this with a type-safe interface, designed so that the external boundary physically cannot receive invalid input. Ah, an idea that people have broken their picks on for 50 years. We will pay out bonuses to employees who worked at least 9 months last year. Actually current employees and former employees. I should create a special type for this case and then depend on the caller to construct such types only for qualifying recipients? That’s just “moving the if” back into the caller. Any responsible developer would validate those entries at the interface…and is it complete?
- quickthrower2 3y agoI think there is a spirit to this. I.e. is it an "if" in spirit. Preconditions are not really branches. They are usually `if (bad) throw;` type of thing. They could be replaced with `assert(!bad);`. A branch would be a function like add_todo_or_calendaritem(is_todo: bool, ...) which would need to branch on is_todo.
- deleted 3y ago[deleted]
- behnamoh 3y agoA similar example of this is OpenAI's API calls which don't do response validation when you do function calling. Essentially, validating the response against the given function(s) is left to the user, leading to various implementations that just make the code noisy. As an alternative, OpenAI could just make sure the true function call is run (first, validate that the response is a JSON, then make sure it's a valid JSON against the provided JSON schema) in n tries, after which the API raises an error or returns None.