proj-oot-ootErrorNotes2

"Also, for Rust 2, I'd suggest exceptions. The "new error handling model"[1] is excessively verbose. Repackaging low-level errors for the next level up requires considerable coding. I understand the big problem with exceptions is what to do about unsafe code. I'd suggest "just say no". Unsafe blocks cannot raise exceptions. If an exception in safe code unwinds into an "unsafe" block, it becomes a panic. Unsafe code in Rust is usually to interface with non-Rust code. If you have unsafe sections in pure Rust code, you're probably doing it wrong.

The secret of exception sanity is a standard exception hierarchy, like Python's. Catching an exception class in Python also gets all its subclasses. "EnvironmentError?" covers all I/O errors, network errors, and such, but doesn't cover internal program errors. New user-defined exceptions are added below the most appropriate part of the standard exception hierarchy. So Python usually doesn't have Java's exception hell. If exceptions go into Rust, do it that way. Require that all exception objects descend from some predefined exception object in the standard exception hierarchy.

An additional advantage of a standard exception hierarchy is that it leads to straightforward internationalization. If all the standard exceptions have translations, users get semi-reasonable error messages in their own language. If you have too many string-type error messages in a program, it's hard to handle multiple languages.

Go is starting to get exceptions through the back door. People are abusing the "panic"/"recover" mechanism in Go to get exceptions.[2] Don't go there.

[1] http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/ [2] https://code.google.com/p/go-wiki/wiki/PanicAndRecover#Usage...


mercurial 14 days ago

> The secret of exception sanity is a standard exception hierarchy, like Python's. Catching an exception class in Python also gets all its subclasses. "EnvironmentError?" covers all I/O errors, network errors, and such, but doesn't cover internal program errors. New user-defined exceptions are added below the most appropriate part of the standard exception hierarchy. So Python usually doesn't have Java's exception hell.

You know, Java also has a standard exception hierarchy. However, whether in Java or Python, it usually makes sense for the exceptions of a library NOT to be part of the standard hierarchy. For instance, SQLAlchemy's exceptions all inherit from SQLAlchemyError?. It's the same thing in Java.

"

---

i might recommend multiply-inheriting exceptions then.. so eg something can be both an SQLAlchemyError?, and an EnvironmentError?.

---

this sort of thing is a real problem:

" You've got a deeply-nested set of objects that may or may not always be there. We've all seen something like this: var myURL = app.config.environment.buildURL('dev'); which leads to one of our favorite javascript errors... error: undefined is not a function

And the solution only makes the code base ugly:

var myURL; if (app && app.config && app.config.environment && app.config.environment.buildURL) { myURL = app.config.environment.buildURL('dev'); }

We all hate that, don't we? " -- https://github.com/letsgetrandy/brototype

or what if it is

app['soap:Envelope']['soap:Body'][0].getResponse[0]['rval'][0].customerId[0]

?

https://github.com/letsgetrandy/brototype is one soln but with a new language we can do better (by not having to repeat the thing twice)

---

make syntactic sugar to have a more structured version of:

        raise Exception('image_mask_onto_atlas_superimpose_pipeline: ABA returned failure; request = %s, parsed response = %s' % (url, data))

(Python lets you put other data in the fields of the Exception, this is too much typing; but if some of these were standardized (eg semantic name of the part of program raising the error; error msg; details) it could be made easier to type)

---

would be really nice if stack tracebacks printed not only everything that Python does, but also:


MichaelOChurch? complains that "Java’s type system lacks the power to verify that a String variable, at compile time, will actually hold a string, and not null, a value that must be handled specially by any method that takes a String (ditto, for any other object type) as input. The string type is not available; you must make do with what is, in fact, a (string option) ref— a mutable reference that may hold a String.".

Which suggests that:

---

to tame event-driven programming, propagation hop limit (preferably continuous, but i don't see how that would work)

(my friend G.B.'s idea)

---

(adjustable) limit on # of statements in a transaction, to guarantee termination

(my friend G.B.'s idea)

---

when transactions fail, retry afteer a raondom time interval to prevent the same sequence of events potentially recurring and causing starvation

---

pervasive patterns builtin to the std library and mb the language:

---

" Here's an example I post sometimes comparing error handling in Go to Haskell's either monad:

    func failureExample()(*http.Response) {
        // 1st get, do nothing if success else print exception and exit
        response, err := http.Get("http://httpbin.org/status/200")
        if err != nil {
            fmt.Printf("%s", err)
            os.Exit(1)
        } else {
            defer response.Body.Close()
        }
    
        // 2nd get, do nothing if success else print exception and exit
        response2, err := http.Get("http://httpbin.org/status/200")
        if err != nil {
            fmt.Printf("%s", err)
            os.Exit(1)
        } else {
            defer response2.Body.Close()
        }
    
    
        // 3rd get, do nothing if success else print exception and exit
        response3, err := http.Get("http://httpbin.org/status/200")
        if err != nil {
            fmt.Printf("%s", err)
            os.Exit(1)
        } else {
            defer response3.Body.Close()
        }
    
    
        // 4th get, return response if success else print exception and exit
        response4, err := http.Get("http://httpbin.org/status/404")
        if err != nil {
            fmt.Printf("%s", err)
            os.Exit(1)
        } else {
            defer response4.Body.Close()
        }
    
        return response4
    }
    
    func main() {
        fmt.Println("A failure.")
        failure := failureExample();
        fmt.Println(failure);
    }

The equivalent Haskell code:

    failureExample :: IO (Either SomeException (Response LBS.ByteString))
    failureExample = try $ do
      get "http://www.httpbin.org/status/200"
      get "http://www.httpbin.org/status/200"
      get "http://www.httpbin.org/status/200"
      get "http://www.httpbin.org/status/404"
    
    main = failureExample >>= \case
      Right r -> putStrLn $ "The successful pages status was (spoiler: it's 200!): " ++ show (r ^. responseStatus)
      Left e -> putStrLn ("error: " ++ show e)" -- https://news.ycombinator.com/item?id=9767846

scoped assertions: true at all times within the given scope (lexical, or variable lifetime)

---

http://marcelog.github.io/articles/erlang_link_vs_monitor_difference.html

---

special error/nil value for "this value isnt ready yet" (eg an unfulfilled promise) and for "havent even begun to compute this yet" (eg a lazy value that hasn't yet been requested, so computation of it hasnt even started) --by default Oot blocks when u look at a value, until that value is ready, so by default u dont see thse, but there is some way to explicitly request them (eg to check on the status of a promise). note that any part of a structure can be uncomputeed while the rest are computed (eg you can have x = [apple/RED lemon/NOT-READY-YET]) -- you need to use recursive any' to check if everything is done (mb 'rany' (recursive-any) or 'dany' (deep-any)? maybe these are just shortcuts for any(deep-flatten(x))? mb we have an is-ready as a shortcut for that w/r/t checking if there are no NOT-READY-YETs or NOT-BEGUN-COMPUTING-YET

probably NOT-BEGUN-COMPUTING-YET is a special case (child class) of NOT-READY-YET

more kinds of errors/classes of errors:

note that these classes are not necessarily orthogonal; eg an error might both be a connection error, and an 'there may or may not have been an error', at the same time.

---

need a sentinel for 'reset'; eg might have a function 'clip' to set a clipping rectange, which takes coordinates as input; pass it this sentinel to turn off clipping again

---

" Take the following C code:

    char* response(messy* message) {
      body* b = message->body;
      if(b==NULL) {
        return NULL;}
      char** lines = b->text;
      if(t==NULL) {
        return NULL;
      }
      if(b->line_count < 11) {
        return NULL;
      }
      return t[10];
    }

...

With Hackell, though, I can refactor out the if statements and get the following equivalent code:

    response :: Messy -> Maybe String
    response message = do
      b <- body message
      lines <- text body
      lines `atMay` 10

...

 With a minimal amount of effort, I could have each failure return a unique error Code.
    errorCode :: Maybe a -> b -> Either b a
    errorCode (Just value) _ = Right value
    errorCode Nothing code = Left code
    response :: Messy -> Either Int String
    response message = do
      b <- body message `errorCode` 1
      lines <- text body `errorCode` 2
      (lines `atMay` 10) `errorCode` 3

...

mightybyte 2 days ago

Just a small side note, you don't even need to write your errorCode function. That and a number of other useful functions are provided in the errors package (http://hackage.haskell.org/package/errors). Also, I like to use string error messages because they're easier to grep for. Here's what your response function would look like with those changes.

    response :: Messy -> Either String String
    response message = do
      b <- note "error in body" $ body message
      lines <- note "error in text" $ text b
      note "error in atMay" $ lines `atMay` 10

...

axman6 2 days ago

I've used a similar construct in dealing with some JSON data recently, using Either String a:

    (?) :: Maybe a -> String -> Either String a
    Nothing ? s = Left s
    Just a ? _ = Right a
    foo :: Value -> Either String Int
    foo val = do
        nested <- val ^? key "foo" . key "bar" ? "Could not find foo.bar"
        mid <- nested ^? nth 2 . _Integral ? "Second element of foo.bar was not an Integral value"
        return mid

which has really helped deal with the stringly typed nature of JSON. I should also mention I stole the idea (and operator name) from one of the Haskell parser combinator libraries which allowed you to name a whole parser expression, so errors would say "could not find ident" instead of "Expected on of a,b,c,d,e,d,f,g,h...., found X"

reply

GregBuchholz? 2 days ago

>I don't know a way of doing that in any of the C style languages

http://www.boost.org/doc/libs/1_49_0/libs/optional/doc/html/...

https://www.google.com/#q=maybe+in+c%2B%2B

...and also I couldn't resist...

    char* response(messy* message) {
        body* b = message->body;
        return b && (b->text) && b->linecount < 11 && b->text[10];
    }

...for a C-ish like language. And that's without creating a "do-notation" macro to erase the &&, as the Haskell do-notation erases the >>=.

reply

evincarofautumn 1 day ago

These aren’t quite the same. With “x && x->y”, you’re using a boolean to encode the assumption that “x” is valid, but with “x >>= getY”, the type system has a proof, which makes refactoring safer.

reply

"

-- https://news.ycombinator.com/item?id=10129307

---

need ' to be able to do:

error2none(x/100.)

(where 'x' might itself be None, generating an error)

the problem is that when x/100. is evaluated, the error is generated, before it is passed into error2none

so we have to take care of that. ' is more than just an ordinary function.

(in Python, you could do:

error2none(lambda x/100.)

def error2none(x): try: return x() except: return

but that's cumbersome

---

"For all the hate Java gets, when I have a checked exception, I know what it is going to be. I don't need to dig around in sourcecode trying to work out if I should care or not. I don't need to check which of the three Golang error-distinction idioms (package-variable errors, error subtypes, plain-old-errors-with-magical-strings) is in play."

---

 jacques_chester 2 days ago

Sorry, typo. "Checked exception", meaning that the compiler enforces error handling and that you can look a method signature and learn what kind of exceptions the method can throw without needing to read its internals.

---

c++ has a 'static_assert' presumably for platform dependent code:

" P.5: Prefer compile-time checking to run-time checking

Reason: Code clarity and performance. You don't need to write error handlers for errors caught at compile time.

Example:

void initializer(Int x) Int is an alias used for integers { static_assert(sizeof(Int)>=4); do: compile-time check

    int bits = 0;                   // don't: avoidable code
    for (Int i = 1; i; i<<=1)
        ++bits;
    if (bits<32)
        cerr << "Int too small\n";
    // ...} "

it also has an 'array_view' which i dont quite understand but presumably it carries array bounds information with it:

" Example; don't:

void read(int* p, int n); read max n integers into *p

Example:

void read(array_view<int> r); read into the range of integers r "

-- [1]

---

in scrapy (which uses Twisted), i see a good (addition?) to Python's stack trace; an '--- <exception caught here> ---' line:

2015-09-30 08:08:08 [twisted] CRITICAL: Unhandled error in Deferred:

Traceback (most recent call last): File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/cmdline.py", line 150, in _run_command cmd.run(args, opts) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/commands/runspider.py", line 88, in run self.crawler_process.crawl(spidercls, opts.spargs) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/crawler.py", line 153, in crawl d = crawler.crawl(*args, kwargs) File "/home/bshanks/.local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1274, in unwindGenerator return _inlineCallbacks(None, gen, Deferred()) --- <exception caught here> --- File "/home/bshanks/.local/lib/python2.7/site-packages/twisted/internet/defer.py", line 1128, in _inlineCallbacks result = g.send(result) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/crawler.py", line 71, in crawl self.engine = self._create_engine() File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/crawler.py", line 83, in _create_engine return ExecutionEngine?(self, lambda _: self.stop()) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/core/engine.py", line 66, in __init__ self.downloader = downloader_cls(crawler) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/core/downloader/__init__.py", line 65, in __init__ self.handlers = DownloadHandlers?(crawler) File "/home/bshanks/.local/lib/python2.7/site-packages/scrapy/core/downloader/handlers/__init__.py", line 17, in __init__ handlers.update(crawler.settings.get('DOWNLOAD_HANDLERS', {})) exceptions.ValueError?: dictionary update sequence element #0 has length 1; 2 is required

---

notes on (and quotes from): http://www.slideshare.net/deanwampler/error-handling-in-reactive-systems

approaches to error handling:

todo: on slide 43

---

esp. when making Exception hierarchies with multiple inheritance, you often want to create Exception classes in a 'faceted' or direct-product kind of way, eg:

class ExceptionDuringRead?(Exception): pass

class BrokerException?(Exception): pass

class BrokerExceptionDuringRead?(BrokerException?, ExceptionDuringRead?): pass

class BrokerTransportException?(BrokerException?): pass

class BrokerTransportExceptionDuringRead?(BrokerException?, ExceptionDuringRead?): pass

class CantUnderstandBrokerException?(BrokerException?): pass

class CantUnderstandBrokerExceptionDuringRead?(CantUnderstandBrokerException?, ExceptionDuringRead?): pass

in this example, it is tedious, and a waste of space, and harder to read, to create all of these classes manually; what you want is just when creating the exception instance itself, to be able to say "create something which is an instance of both CantUnderstandBrokerException? and ExceptionDuringRead?".

In other words, we need lightweight syntax for the task of creating an instance of an anonymous class with a given set of superclasses.

---

an approach to separating error handling from happy path:

https://github.com/MichaelDrogalis/dire

summary: wrappers for functions that redefine the fn name to be to wrapped fn. Library support for various things like preconditions and postconditions.

---

if we have a 'maybe tower' that is, something like maybe(maybe(maybe(...(value)))), then we can compress it to maybe-tower(value, array-of-maybe-arguments) (by 'maybe arguments' i mean that i am assuming that our 'maybe' is really more like Haskell's Either than Haskell's Maybe, eg if it is a Nothing instead of a Just there can be an Exception attached (or other value? not sure..)). For uniformity we can just always use maybe-tower and never Maybe, in the implementation.

---

low-level approaches to error:

in case of error, trap/raise; or return error; or saturate; or wrap-around; or set error flag/error status register? return value, or return tuple (result, errorcode) (or Maybe)?

---

"

jacques_chester 1 day ago

The problem is that microservices are not objects. They leak reality into your problem domain in a way that simply cannot be made to go away.

If regular object oriented programming languages had method calls that randomly failed, were delayed, sent multiple copies of a response, changed how they behaved without warning, sent half-formed responses ... then yes it would be the same.

Distributed systems are hard, because you cannot change things in two places simultaneously. All synchronisation is limited by the bits you can push down a channel up to, but not exceeding, the speed of light. In a single computer system this problem can be hidden from the programmer. In a distributed system, it cannot.

Probably the most devastating critique of the position that "it's just OO modeling!" came in A Note on Distributed Computing, published in 1994 by Waldo, Wyant, Wollrath and Kendall[0]:

"We look at a number of distributed systems that have attempted to paper over the distinction between local and remote objects, and show that such systems fail to support basic requirements of robustness and reliability. These failures have been masked in the past by the small size of the distributed systems that have been built. In the enterprise-wide distributed systems foreseen in the near future, however, such a masking will be impossible."

[0] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.7...

reply "


 oconnor663 5 hours ago

In cases where the caller might want to handle an error, Rust almost always uses an Option/Result. For vectors, the `get` method lets you try to get an element out without panicking if your index is bad. (Or of course you can just check the index manually. This is guaranteed to be threadsafe, which is nice.) The try! macro is the standard way to say "I don't want to handle errors here -- you take care of it."

---

Here is a list of basic errors in Python. Only builtin ones, I'm not talking about ones from other libs. And I'm talking of error categories, as we all know one exception can be used in a lot of different contexts:

  BaseException
   +-- SystemExit
   +-- KeyboardInterrupt
   +-- GeneratorExit
   +-- Exception
        +-- StopIteration
        +-- StopAsyncIteration
        +-- ArithmeticError
        |    +-- FloatingPointError
        |    +-- OverflowError
        |    +-- ZeroDivisionError
        +-- AssertionError
        +-- AttributeError
        +-- BufferError
        +-- EOFError
        +-- ImportError
        +-- LookupError
        |    +-- IndexError
        |    +-- KeyError
        +-- MemoryError
        +-- NameError
        |    +-- UnboundLocalError
        +-- OSError
        |    +-- BlockingIOError
        |    +-- ChildProcessError
        |    +-- ConnectionError
        |    |    +-- BrokenPipeError
        |    |    +-- ConnectionAbortedError
        |    |    +-- ConnectionRefusedError
        |    |    +-- ConnectionResetError
        |    +-- FileExistsError
        |    +-- FileNotFoundError
        |    +-- InterruptedError
        |    +-- IsADirectoryError
        |    +-- NotADirectoryError
        |    +-- PermissionError
        |    +-- ProcessLookupError
        |    +-- TimeoutError
        +-- ReferenceError
        +-- RuntimeError
        |    +-- NotImplementedError
        |    +-- RecursionError
        +-- SyntaxError
        |    +-- IndentationError
        |         +-- TabError
        +-- SystemError
        +-- TypeError
        +-- ValueError
        |    +-- UnicodeError
        |         +-- UnicodeDecodeError
        |         +-- UnicodeEncodeError
        |         +-- UnicodeTranslateError

---

e28eta 2 hours ago

Something I find very interesting about Swift is it's complete reversal of opinion from Objective-C on this topic.

Obj-C basically has the behavior GP wants: call a method on nil (aka: null) and it returns nil/false/0. It's possible to write concise & correct code that relies on this behavior.

Swift turns it into a fatal runtime error, and uses the type system to help prevent it from happening.

I think there's room for both (probably not in the same project though!). A lot of successful projects have been written in Obj-C, and some of them rely on the behavior of nil.

However, it's harder to maintain the code. You have to reason about whether nil is possible and does the code do the right thing, or did the original author forget to consider the possibility? It's really nice when the static type system forces the programmer to be explicit.

Having used both paradigms, I honestly don't know which I'd prefer for JS - especially considering it's lack of static typing. It might depend on whether I was writing application or library code.

reply

spankalee 1 hour ago

There is room for both, but it should be explicit in my opinion. Dart for instance has null safe operators

  // not null-safe:
  foo.bar.baz();
  // null-safe:
  foo?.bar?.baz();

If your type system also does null tracking, then you can see where you might have null values and decide to use the null-safe operators.

reply

---

so: consider using foo? instead of something like foo', b/c foo? is more well-known (are these the same?)

---

Rust notes that error messages regarding the interaction between two spots in code should look like this:

https://blog.rust-lang.org/images/2016-08-09-Errors/new_errors.png [2]

---

 thinkpad20 87 days ago | parent [-] | on: Putting Down Elm

Many of the error messages become clearer with experience, but this is mostly due to developing an intuition for what is actually wrong with your code based on the error message you see, not due to the helpfulness of the error message. Often the actual source of the error is in a different place than what is reported. Consider what happens if you mean to concatenate two strings but forget to put `++` in there:

    Prelude> "foo" "bar"
    <interactive>:3:1:
        Couldn't match expected type ‘[Char] -> t’
                    with actual type ‘[Char]’
        Relevant bindings include it :: t (bound at <interactive>:3:1)
        The function ‘"foo"’ is applied to one argument,
        but its type ‘[Char]’ has none
        In the expression: "foo" "bar"
        In an equation for ‘it’: it = "foo" "bar"

Yikes. Even worse, consider a similar situation with numbers:

    Prelude> 1 2
    <interactive>:2:1:
        Non type-variable argument in the constraint: Num (a -> t)
        (Use FlexibleContexts to permit this)
        When checking that ‘it’ has the inferred type
          it :: forall a t. (Num a, Num (a -> t)) => t

Also the parser errors in Haskell are terrible. That the community has so long put up with "parse error (possibly incorrect indentation or mismatched brackets)" is a marvel to me, and is one of the most irritating errors to fix because of the (at least apparent) simplicity of improving it.

ob 87 days ago [-]

And that is exactly why people love Elm:

  > "foo" "bar"
  -- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
  
  You are giving an argument to something that is not a function!
  
  3|   "foo" "bar"
             ^^^^^
  Maybe you forgot some parentheses? Or a comma?

Also

  > "foo" + "bar"
  -- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm
  
  The left argument of (+) is causing a type mismatch.
  
  3|   "foo" + "bar"
       ^^^^^
  (+) is expecting the left argument to be a:
  
      number
  
  But the left argument is:
  
      String
  
  Hint: To append strings in Elm, you need to use the (++) operator, not (+).
  <http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#++>

---

alexatkeplar 781 days ago [-]

Here is a JSON Schema validation failure taken straight out of the Snowplow test suite (pretty printed):

  {
    "level": "error",
    "schema": {
      "loadingURI": "#",
      "pointer": ""
    },
    "instance": {
      "pointer": ""
    },
    "domain": "validation",
    "keyword": "required",
    "message": "object has missing required properties ([\"targetUrl\"])",
    "required": [
      "targetUrl"
    ],
    "missing": [
      "targetUrl"
    ]

---

it's cool that python3 reports if an exception arose within another exception:

>>> h = {} >>> try: ... h[2] ... except: ... raise Exception("hi") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> KeyError?: 2

During handling of the above exception, another exception occurred:

Traceback (most recent call last): File "<stdin>", line 4, in <module> Exception: hi

---

brandur 7 days ago [-]

I've taken a very similar path recently myself and started looking into Rust.

> 2. Proper error handling. I love error checking

Hugely agree here. I can get behind Go's overall mentality of returning errors instead of throwing exceptions, but in my mind there are not enough primitives in the language to keep this style of coding safe and sustainable.

Rust's `Result` type, `try!` macro, and pattern matching are an incredibly powerful combination, and make Go's approach look downright medieval in comparison.

Overall though, Go is probably still the more practical choice between the two languages (due to Rust's incredibly high barrier to entry).

reply

---

jerf 7 days ago [-]

"Channels without panics. Channels are awesome, but Go's design of them means that you have to learn special ways to design your usage of channels so that it does not crash your program. This is asinine to me. So much type safety exists in Go, and yet it's so easy for a developer to trip over channels and crash their program."

I've solved this in my programming by finally coming to grips with the fact that channels are unidirectional, and if you want any bidirectional communication whatsoever, up to and including the client telling the source for whatever reason it has closed, even that one single bit, that must occur over another channel of its own. Clients must never close their incoming channels. This does mean that many things that Go programmers sometimes try to bash into one channel need to be two channels.

But I agree it's a problem.

reply

masklinn 7 days ago [-]

> I've solved this in my programming by finally coming to grips with the fact that channels are unidirectional, and if you want any bidirectional communication whatsoever, up to and including the client telling the source for whatever reason it has closed, even that one single bit, that must occur over another channel of its own. Clients must never close their incoming channels. This does mean that many things that Go programmers sometimes try to bash into one channel need to be two channels.

Erlang got it right (again), by sending messages to processes they can only be unidirectional.

reply

jerf 7 days ago [-]

I see Erlang and Go as duals of each other here, at least considered locally; Erlang focuses on the destination and Go focuses on the conduit of the message. Each have advantages and disadvantages. I think Erlang's approach ends up easier to use, but you lose the useful ability of channels to be multi-producer and multi-consumer.

(I'd like to see Erlang create a concept of a multi-mailbox where you can have a PID that can be picked up by a pool. Trying to create pools from within Erlang proper is quite challenging, and the runtime could do better. I acknowledge the non-trivial problems involved with clustering; I think it'd still be a big improvement even if they only work on the local node.)

The Erlang equivalent is that messages don't carry their source with them, so you must embed the source in the message if you want to send a message back. The main difference here is that Erlang doesn't offer anything that can be misunderstood as sending back a message any other way, so nobody is fooled and this never comes up as a problem once someone gets Erlang. The problem in Go is that the client end of the channel is capable of closing the channel, even though it really seriously never should.

reply

---

fizzbatter 7 days ago [-]

I don't know how, but frankly i'd love to eliminate all simple panics. Nil pointers and channels seem two big culprits, offhand.

Granted, i left out nil pointer/interface panics because it seems unrealistic given how difficult it was for Rust to get rid of nil pointers. I'm not sure Go 2.0 could do it and still be considered Go.

reply

pcwalton 7 days ago [-]

> Unlike many of the complaints about Go that would require fundamental restructuring, C# showed that actually can be retrofitted onto a language without breaking it.

C# added nullable types. Not non-nullable types.

In Go, the concept of zero values is so fundamentally baked into the semantics that non-nil pointers can never really be added to it.

reply

pcwalton 7 days ago [-]

> Granted, i left out nil pointer/interface panics because it seems unrealistic given how difficult it was for Rust to get rid of nil pointers.

That wasn't difficult at all.

reply

---

greendragon 7 days ago [-]

Curious what they weren't so happy about with Python? Was it purely performance? If so, did they consider PyPy?, or at least profile what the slowest bits are so they can evaluate whether to throw everything out or just rewrite the slow bits? Was it the language itself? Not everyone likes dynamic languages, though it's odd they started with it. Did you consider Node at all?

reply

snovv_crash 7 days ago [-]

From my time doing server backend python dev, it is only catching any problems at runtime, everything from missing arguments to typos in variable names that accidentally match another variable, turning your int to a string. Having a compiler catch these saves much time and hairpulling. And having unit tests as a final defense, rather than the only defense, does wonders for my peace of mind.

reply

---

Rust uses ? to mark potential exception propagation points:

" The ? operator

Rust has gained a new operator, ?, that makes error handling more pleasant by reducing the visual noise involved. It does this by solving one simple problem. To illustrate, imagine we had some code to read some data from a file:

fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("username.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }}

This code has two paths that can fail, opening the file and reading the data from it. If either of these fail to work, we’d like to return an error from read_username_from_file. Doing so involves matching on the result of the I/O operations. In simple cases like this though, where we are only propagating errors up the call stack, the matching is just boilerplate - seeing it written out, in the same pattern every time, doesn’t provide the reader with a great deal of useful information.

With ?, the above code looks like this:

fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("username.txt")?; let mut s = String::new();

    f.read_to_string(&mut s)?;
    Ok(s)}

The ? is shorthand for the entire match statements we wrote earlier. In other words, ? applies to a Result value, and if it was an Ok, it unwraps it and gives the inner value. If it was an Err, it returns from the function you’re currently in. Visually, it is much more straightforward. Instead of an entire match statement, now we are just using the single “?” character to indicate that here we are handling errors in the standard way, by passing them up the call stack.

Seasoned Rustaceans may recognize that this is the same as the try! macro that’s been available since Rust 1.0. And indeed, they are the same. Before 1.13, read_username_from_file could have been implemented like this:

fn read_username_from_file() -> Result<String, io::Error> { let mut f = try!(File::open("username.txt")); let mut s = String::new();

    try!(f.read_to_string(&mut s));
    Ok(s)}

So why extend the language when we already have a macro? There are multiple reasons. First, try! has proved to be extremely useful, and is used often in idiomatic Rust. It is used so often that we think it’s worth having a sweet syntax. This sort of evolution is one of the great advantages of a powerful macro system: speculative extensions to the language syntax can be prototyped and iterated on without modifying the language itself, and in return, macros that turn out to be especially useful can indicate missing language features. This evolution, from try! to ? is a great example.

One of the reasons try! needs a sweeter syntax is that it is quite unattractive when multiple invocations of try! are used in succession. Consider:

try!(try!(try!(foo()).bar()).baz())

as opposed to

foo()?.bar()?.baz()?

The first is quite difficult to scan visually, and each layer of error handling prefixes the expression with an additional call to try!. This brings undue attention to the trivial error propagation, obscuring the main code path, in this example the calls to foo, bar and baz. This sort of method chaining with error handling occurs in situations like the builder pattern.

Finally, the dedicated syntax will make it easier in the future to produce nicer error messages tailored specifically to ?,

" [3]


Scaevolus 6 days ago [-]

The ? operator looks lovely. It's like the C# null conditional operator, but for the Result pattern:

    try!(try!(try!(foo()).bar()).baz())

Becomes

    foo()?.bar()?.baz()?

Versus the Go equivalent:

    a, err := foo()
    if err != nil {
        return nil, err 
    }
    b, err := a.bar()
    if err != nil {
        return nil, err 
    }
    c, err := b.baz()
    if err != nil {
        return nil, err 
    }
    return c

reply

Animats 6 days ago [-]

Versus the Python equivalent:

    try:
        foo().bar().baz()
    except Exception as message: 
        return message

reply

---

could combine monadic error handling with exceptions in that could have syntactic sugar for 'Maybe' which also has an error class attached (including class, line of origin, call stack trace)

---

spion 1 day ago [-]

I think Swift got almost everything right, except for not having a GC maybe. Especially everything related to exceptions: the combination of "throws", "defer" and "try"/"try!" gets rid of most of the classic problems with exceptions.

reply

---

from golang:

" Detection of concurrent map accesses

Improvement on Go 1.6.

    const workers = 100 // what if we have 1, 2, 25?
    var wg sync.WaitGroup
    wg.Add(workers)
    m := map[int]int{}
    for i := 1; i <= workers; i++ {
        go func(i int) {
            for j := 0; j < i; j++ {
                m[i]++
            }
            wg.Done()
        }(i)
    }
    wg.Wait()

Outputs:

fatal error: concurrent map read and map write fatal error: concurrent map writes

"

---

" ...exception handling is either implicit or explicit depending on how a method is invoked! ... " [4]

" Mishandled Exceptions In Ethereum, there are several ways for a contract to call another, e.g. , via send instruction or call a contract’s func- tion directly ( e.g. , aContract.someFunction() ). If there is an exception raised ( e.g. , not enough gas, exceeding call stack limit) in the callee contract, the callee contract terminates, reverts its state and returns false . However, depending on how the call is made, the exception in the callee contract may or may not get propagated to the caller. For example, if the call is made via the send instruction, the caller con- tract should explicitly check the return value to verify if the call has been executed properly. This inconsistent exception propagation policy leads to many cases where exceptions are not handled properly. As we later show in Section 6, 27 . 9% of the contracts do not check the return values after call- ing other contracts via send ... A better solution is to automatically propagate the excep- tion at the level of EVM from callee to caller; this can be easily implemented but requires all clients to upgrade. We can additionally provide a mechanism for proper exception handling, e.g. , by having explicit throw and catch EVM in- structions. If an exception is (implicitly or explicitly) thrown in the callee and not properly handled, the state of the caller can be reverted. " [5]

---

Solidity distinguishes between 'assert' (which should never fail; if there is any codepath in which it may fail, that could be a compile-time error) and 'require' (which "shouldn't" fail but actually could; for checking inputs and such)

" The assert function should only be used to test for internal errors, and to check invariants. The require function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing assert. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix. "

---