
On 8/06/2017 23:36, Niall Douglas wrote:
It doesn't especially help you in practice. What Outcome currently does is return magic type sugar from TRY which is implicitly constructible by any basic_monad instance, this works around needing to know what the return type is for the calling function in the TRY implementation.
Fair enough. That could still work for arbitrary EI types though -- you automatically know what the type used by the method you're calling is, and you can assume that the caller either returns the same thing or something compatible with it, as I said below. Otherwise it will be a compiler error, and rightly so.
Besides, if the intent is to convey the payload up the call chain, then it can be left to the responsibility of the method itself to forward that appropriately -- ie. if you call something that can return an error_code_info<EI1> then you are required to do one of the following:
1. Deal with the error locally, not calling TRY. 2. Call TRY to pass on the error unmodified, thereby requiring that you also return an error_code_info<EI1>. 3. Pass on the error, but returning error_code_info<EI2> where EI2 is constructible from EI1 (forwarding the original payload plus some additional context). This might be a special case of #1.
(By "returning" here I don't mean directly, I mean wrapped in a result<> or outcome<>.)
Of course you could do all that.
What I'm saying is that for 95% of programmers, when facing the choice of whether to use different E types in exchange for typing a ton load more boilerplate all day long every day in everything they write, they will - I guarantee you - choose just to make the error type the same throughout the entire program. It makes things *simple*.
People don't seem to have a problem with multiple exception classes carrying different payloads all derived from std::exception. This is an orthogonal idea, albeit simplifying it with only a single templated derived type. (I'm not opposed to having separate derived types either, but I think the templated design is better since you can provide better guarantees to the outcome implementation.) I don't think it'd be as hard to grasp as you're making out. If we take Filesystem as an example, it's likely that most methods would return an error_code_info<filename_info> that contains the filename affected by the error (which you might argue is useless to the caller since it already knows, but again imagine this being bubbled several levels up the call stack to code that didn't know the filename any other way). A few might return a plain error_code because they didn't have a filename to work on. Something like copy_file might return a type derived from filename_info that adds a second filename. That sort of thing. Or maybe Filesystem itself would only return plain error_codes, but then the caller (who constructed a filename unknown to the grandparent) might be the one that wants to include the actual filename when passing on the error, since it knows that its caller has no other way to know it.