Hacker News new | ask | show | jobs
by lerno 1777 days ago
I'm experimenting with a solution in C3 that has this behaviour. I don't have a sum type as such, but the binding acts as one. I call the binding a "failable".

    int! a = getMayError();
    // a is now either an int, or contains an error value.

    // foo(a) is only conditionally invoked.
    int! b = foo(a);

    // The above works as if it was written:
    // int! b = "if a has error" ? "the error of a" : foo("real value of a");

    // A single if-catch with return will implicitly unwrap:
    if (catch err = b) {
       /* handle errors */
       return;
    }
    /* b is unwrapped implicitly from here on and is treated as int */

    if (try int x = a) {
       /* conditionally execute if a isn't an error */
    }
I think this is kind of formalizing the poison technique but external from the call (that is, "foo" does not need to know about "failables" or return one, the call is skipped on the caller side). Here are some more examples: http://www.c3-lang.org/errorhandling/

I'd be interested in hearing what you think about this (experimental) solution Walter.

1 comments

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 :)

No 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`.
Hm. 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.)

What 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!