On Thu, Apr 25, 2013 at 10:00 PM, Brian Anderson <[email protected]>wrote:
> On 04/25/2013 07:18 AM, Gábor Lehel wrote: > >> >> Whether to respond to failure by failing the task or by indicating it in >> the return value seems like it would be better handled by having separate >> functions for each. In the case where you expect success, the result >> shouldn't be an Option. If the result is an Option, it shouldn't fail the >> task (whether or not a condition handler is present). So for example: >> >> open<P: PathLike>(path: &P, mode: FileMode, access: FileAccess) -> >> FileStream >> >> try_open<P: PathLike>(path: &P, mode: FileMode, access: FileAccess) -> >> Option<FileStream> >> >> If the expect-success version of a function returns (), the try_ version >> would return bool rather than Option<()> (which are isomorphic). >> >> Upon encountering an error, open() would raise a condition, and then >> continue if possible, otherwise fail!(). >> >> I'm not sure whether try_open() would raise a condition or not. The way >> conditions were described, if they aren't handled, the task fails. >> try_open() definitely shouldn't fail. Would it be reasonable to raise a >> condition and instead of failing, return None if it's not handled? If not, >> then try_open() shouldn't raise any conditions, and should just return None. >> >> The obvious drawback is twice as many functions, but it feels preferable >> to forcing two different behaviours onto the same function. The fast >> (expect-success) path would be just as clean as before, if not cleaner. >> >> [1] https://mail.mozilla.org/pipermail/rust-dev/2012-October/002545.html >> > > With this strategy could we make the `try_` return values return > `Result<FileStream, SomeErrorType>` (not using conditions at all)? Don't see why not. The other option would be to return Option and provide a (task-local) function to retrieve the details of the last error (I think I saw something like that mentioned somewhere). I don't have a clear preference. > Then nil-returning functions would need to be `Option<SomeErrorType>`. > Having two ways to perform every I/O operation will 'infect' all code that > uses I/O indirectly. For instance, we have a `ReaderUtil` trait that > defines maybe two dozen methods. Do those all get duplicated? That seems > pretty disastrous to me. > That's a good point. If all higher-up IO libraries end up having to write two versions of everything too, that's pretty bad. Ideally, you'd want to have everything do it one way consistently, and provide some convenient method to translate it to the other way. But if the default is condition-raising/fail!()ing, then the translation to Option involves spawning a task which is unacceptably expensive, and if the default is Option, then the translation to fail!() involves mucking with Options, which makes straightline expect-success code unacceptably ugly. It feels like a failure of the language if there's no adequate way to abstract this out, honestly, I don't know what else to say. None of the options look appealing. (If you have to abuse conditions and Options in counteridiomatic ways to cram both behaviours into half the functions in a workable way, that doesn't reflect well on the language either.) If the recoverable/substitute-providing condition raising versions can be done satisfactorally, would be that remove the need for the try_ versions? I think you'd still want some way to say "okay, give up on opening the file, let's do something else instead", without failing the task. Which is presumably what NullFileStream would be about. But removing these kinds of hidden error conditions is what Option is supposed to be about. Going in circles... > You mentioned disliking the giant IoError enum, and I agree, but I don't > know a better way to represent it. We don't have subclassing to create an > Exception type hierarchy. Do you have any suggestions? > I was only thinking that instead of condition! { io_error: super::IoError -> (); } you might have condition! { file_not_found_error: super::FileNotFoundError -> Foo; } condition! { file_permission_error: super::FilePermissionError -> Bar; } and so forth. In other words, instead of the branching in the IoError enum, branching at the condition and error-describing-type level. That only makes sense in the as-you-would-expect-a-condition-system-to-work scenario though. (FWIW, it might be possible to emulate an exception hierarchy Haskell-style[1] like: trait Exception: Typeable { fn to_exception<'lt>(&'lt self) -> &'lt Exception; fn from_exception<'lt>(&'lt Exception) -> Option<&'lt Self>; } trait Typeable { // does anything like this exist already? fn type_of(&self) -> TypeRep; } impl Eq for TypeRep { ... } fn cast<T: Typeable, U: Typeable, 'lt>(x: &'lt T) -> Option<&'lt U> { unsafe { ... } } fn cast_obj<U: Typeable, 'lt>(x: &'lt Typeable) -> Option<&'lt U> { unsafe { ... } } Dunno if I got self/Self and lifetimes and all of that right. IOW an Exception object can wrap any exception (to_exception) and you can test whether it's an instance of any particular exception with from_exception (which would typically be implemented with cast_obj). And you can make similar traits further down the 'hierarchy'. Don't know if this would be any good, just saying.) [1] http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html > -Brian > > -- Your ship was destroyed in a monadic eruption.
_______________________________________________ Rust-dev mailing list [email protected] https://mail.mozilla.org/listinfo/rust-dev
