3 ms·
So you’ve built in monadic bind for the Either monad into the language: Right x >>= f = Right (f x) -- normal case Left y >>= f = Left y -- error propagat
by mananaysiempre 5y ago
So you’ve built in monadic bind for the Either monad into the language:
Right x >>= f = Right (f x) -- normal case
Left y >>= f = Left y -- error propagation case
(The slogan is “monadic bind is an overload for the semicolon”.)
I don’t expect this knowledge will dramatically change what you’re doing, but now that you know that’s how some people call it you have one more place to steal ideas from :)
- lerno 5y agoNo I'm quite aware of this. It's a restricted, implicit variant of it. But not also that it's not the type but the binding, which makes it slightly different from using a `Result`.
- mananaysiempre 5y agoHm. OK. I tried writing a response several times but I still feel confused. Can you explain what you mean by “not the type but the binding”? Note that I know the Haskell but not the Rust (guessing from the “Result” name) way of working in this style. (Not necessarily relevant or correct thoughts: - Your language still seems to mark potentially-failed values in the type system, even if it writes them T! not Either Error T or Result<Error, T>; - The way Haskell’s do-notation [apparently implemented as a macro package in Rust] is centred around name binding seems very close to what you’re doing, although it [being monadic, not applicative] insists on sequencing everything, so fails the whole block immediately once an error value occurs; - Of course, transparently morphing a T-or-error into a T after a check for an error either needs to be built into the language or requires a much stronger type system; Haskell circumvents this by saying that x <- ... either gives you a genuine T or returns failure immediately, which is indeed not quite what you’re doing.)
- lerno 5y agoWhat I mean by saying "it's a binding" is that it is a property of the variable (or return channel of a function) rather than a real sum type. Consequently it does not participate in any type conversions and you cannot pass something "of type int!" because the type does not exist. Here is an example: int! x = ... int*! y = &x; int**! z = &y; // If it had been a type then // int!* y = &x; // int!** z = &y; // int*! y = &x; // means // int*! y = "if x is err" ? "error of x" // : "the address holding the int of x" This also means that `int!` cannot ever be a parameter, nor a type of a member inside of a struct or union. The underlying implementation is basically that for a variable `int! x` what is actually stored is: // int! x; int x$real; ErrCode x$err; // int*! y; int* y$real; ErrCode y$err; // y = &x; if (x$err) { y$err = x$err; } else { y$real = &x$real; } int z; // y = &z; y$err = 0; y$real = &x; The semantics resulting from this is different from if `int!` had been something like struct IntErr { bool is_err_tag; union { int value; ErrCode error; }; }; Which is what a Result based solution would work like. In such a solution: int! x ... ; int!* y = &x; // Ok int z = ... y = &z; // <- Type error!