[HN Gopher] Multitasking, parallel processing, and concurrency i...
       ___________________________________________________________________
        
       Multitasking, parallel processing, and concurrency in Swift
        
       Author : ingve
       Score  : 60 points
       Date   : 2024-06-10 07:55 UTC (1 days ago)
        
 (HTM) web link (eclecticlight.co)
 (TXT) w3m dump (eclecticlight.co)
        
       | behnamoh wrote:
       | Tangent: I love Swift's new typed throws! One frustration with
       | Python is you can't--just by looking at function signature--tell
       | that it raises exceptions. With Swift (and afaik, Java) you would
       | write it in the function definition, but still, you wouldn't know
       | what _type_ of exception to handle. Now that problem is solved!
       | 
       | More generally though, I wish we could avoid having exceptions to
       | begin with. What's the reason behind their prevalence in almost
       | every language?
        
         | animal_spirits wrote:
         | We couldn't let go of GOTO :)
        
         | jiehong wrote:
         | In Java, typed exceptions are now considered an antipattern,
         | because it caused problems in multiple parts of the language
         | such as in lambdas. Results from rust tend to be more
         | composable.
        
           | jkubicek wrote:
           | Do you know of any blogs or writing on this that I could
           | read?
           | 
           | Naively, untyped exceptions in Swift always felt like an
           | obvious type system loophole that we should be closing as
           | soon as possible.
           | 
           | I trust that the "typed exceptions are an antipattern" people
           | know what they're talking about, but I really don't
           | understand the reasoning behind that position.
        
             | MBCook wrote:
             | I've heard that claim many times before. I'm a daily Java
             | developer, and can provide a little perspective on what
             | Java is like. Though I don't know the exact specifics of
             | the claim.
             | 
             | In Java a given method either doesn't throw any exceptions,
             | or only throws the named ones and their children. So
             | nothing, or maybe just InvailArgumentException (and its
             | descendants).
             | 
             | However there's a lot of different kinds of exceptions. So
             | very quickly you tend to end up with one of two things.
             | Functions have a big list of possible exceptions (an A, or
             | B, or C, or F, or G) or the far more common in company
             | code: throws Exception. Don't bother to list stuff, just
             | say "I throw stuff".
             | 
             | Except, that's all a lie. Because every function
             | _implicitly_ can throw RuntimeException and its children.
             | Things like OutOfMemory or StackOverflow.
             | 
             | So basically all code everywhere realistically has to be
             | aware of exceptions. A lot of people get lazy and don't
             | bother to list the actual kinds of exceptions they throw or
             | pick one that's a runtime exception so they don't have to
             | add the 'throws' to the signature to every single method.
             | 
             | And how do you solve calling a method with a checked
             | exception? Well the right thing to do is use a try block,
             | but so often people just add it to their own signature and
             | pass the buck.
             | 
             | So a lot of the benefits just sort of get lost.
             | 
             | Swift now has three options: doesn't throw, throws
             | something, throws of type X. You can't have multiple types.
             | And you can't pretend the exception doesn't exist and just
             | let it bubble up, you have to put a 'try' on the statement
             | line to do that.
             | 
             | As someone who works in both languages Swift's approach
             | does seem like an improvement. I'm not sure how big.
             | 
             | But just in Swift, being able to explicitly state the one
             | type of the exception that may be thrown seems like a very
             | nice add for type safety and reducing boilerplate checks.
        
               | koito17 wrote:
               | Minor nitpick: OutOfMemoryError and StackOverflowError
               | are subclasses of Error, not Exception. It is an anti-
               | pattern to catch any Error subclasses since these are
               | usually JVM-internal exceptions. Programs are firmly in
               | undefined behavior territory when they e.g. catch an
               | OutOfMemoryError and do nothing with it. This is why
               | catching Throwable is considered a bad idea compared to
               | Exception. In the majority of cases, there is no
               | reasonable recovery from a thrown Error, unlike a thrown
               | Exception.
               | 
               | With that said, it is true RuntimeException and its
               | subclasses are unchecked exceptions. Thankfully, those
               | exceptions tend to be things like
               | IllegalArgumentException, NullPointerException, etc.,
               | which are still pretty serious but not as bad as the two
               | examples given.
        
               | MBCook wrote:
               | Ah that's right. I'm used to RuntimeException since
               | that's what people subclass to get out of the checked
               | exception mess. The exact hierarchy around rare things
               | like OutOfMemory just isn't something that ever comes up
               | in day to day work so it's easy to forget.
               | 
               | Thanks for the correction.
        
             | kccqzy wrote:
             | It's not a type system loophole in the sense that the type
             | system is supposed to contain subtyping as a feature. In
             | Java you could be unhelpful and pass every argument as type
             | Object. (Of course the language allows you to cast the type
             | afterwards.) That's not the fault of the type system it's
             | the people using it. Do you hear people proposing to ban
             | the use of Object in function arguments? No? Because it's a
             | style issue not a type system issue.
        
               | MBCook wrote:
               | You're right but I think that's pretty self-defeating so
               | people wouldn't really do it.
               | 
               | But there's a pretty good argument that the way
               | exceptions are implemented in Java makes the easiest
               | thing to do simply be to start tacking "throws Exception"
               | on the end of tons of methods once you start to have
               | trouble. Everywhere I've worked you start to see that in
               | chunks of the code base.
               | 
               | You don't have to. It's certainly avoidable. But as
               | things get more and more complicated people will just opt
               | for that to save themselves time. Java's design
               | unintentionally encourage it when the going gets rough.
               | Even when using an IDE that can add the correct types to
               | the list for you, that's what people reach for.
        
             | Tainnor wrote:
             | In my opinion, exceptions should be for really exceptional
             | situations - things you can't realistically expect and/or
             | which just signal a programming error (e.g. a precondition
             | being violated). They aren't usually recoverable locally
             | and should bubble up the stack, so you can handle them at
             | some boundary (e.g. so you can serve up a 500 page to a
             | request without crashing the entire server for everyone).
             | 
             | For things that are expected to go wrong sometimes,
             | algebraic data types (including Result types) are better.
             | They are part of the type system, so everything you can do
             | with types, you can do with them, including making
             | functions parametric over whether they return an "error" or
             | not. Exceptions need extra mechanisms so that functions can
             | be parametric over them (e.g. Swift has "rethrows", Java
             | has... nothing). They aren't really composable with
             | anything else.
        
             | svieira wrote:
             | I haven't heard anyone anywhere say that "typed exceptions
             | are an antipattern" just "typed exceptions _as implemented
             | by Java_ are an antipattern". The general problem is well
             | set forth by Anders Hejlsberg, Bill Venners and Bruce Eckel
             | in _The Trouble With Checked Exceptions_ [1]. Briefly,
             | typed exceptions in Java are not genericizable. Everything
             | must be known at compile time. That is to say, you cannot
             | write:                   interface ThrowingRunnable<E
             | extends Throwable> {           void run() throws E;
             | }
             | 
             | Instead, the best you could do is manual multiplication of
             | interfaces:                   interface AThrowingRunnable {
             | void run() throws ExceptionA;         }
             | interface BThrowingRunnable {           void run() throws
             | ExceptionB;         }              // ... snip thousands
             | more ...
             | 
             | Or give up and erase all the type information by saying
             | `throws Exception` or `throws Throwable`.
             | 
             | Genericizing throws in particular was tried in Midori [2]
             | and worked out really well (by report). In addition,
             | several less-than-completely-obscure languages are starting
             | to experiment with the algebra of effects in general (as
             | opposed to error handling in particular). Pony [3], OCaml
             | [4], and others are experimenting with bringing what Koka
             | [5] (among others) to the masses.
             | 
             | [1]: https://www.artima.com/articles/the-trouble-with-
             | checked-exc...
             | 
             | [2]: https://joeduffyblog.com/2016/02/07/the-error-model/
             | 
             | [3]: https://www.ponylang.io/
             | 
             | [4]: https://ocaml.org/manual/5.2/effects.html
             | 
             | [5]: https://koka-lang.github.io/koka/doc/index.html
        
               | sc22 wrote:
               | You _can_ write in Java that very interface you claimed
               | could not be written.                  interface
               | ThrowingRunnable<E extends Throwable> {          void
               | run() throws E;        }
               | 
               | Only, hardly anybody writes it that way, so people forget
               | that it's possible. Why don't people use exception
               | polymorphism in that way? I am not sure, but I believe it
               | would make code too verbose, since every function
               | containing a virtual method call to something that might
               | throw would have to be parameterized that way.
        
             | Someone wrote:
             | > I trust that the "typed exceptions are an antipattern"
             | people know what they're talking about, but I really don't
             | understand the reasoning behind that position.
             | 
             | The reasoning is that they force a contract upon
             | implementations that they may not be able to implement in
             | spirit.
             | 
             | Let's say a library defines a container interface that says
             | a method can throw a NoSuchElementException.
             | 
             | Now, I implement that interface to implement a disk backed
             | container. What do I do when that code encounters an
             | _IOException_? Swallow it (very bad), throw an
             | _NoSuchElementException_ , possibly wrapping the
             | IOException inside it, if possible (highly confusing, as
             | now the class changed the meaning of
             | _NoSuchElementException_ ), or throw an unchecked exception
             | that wraps it instead?
             | 
             | Most Java code would pick that last option, and you end up
             | with a nice interface that says that a call can throw
             | _NoSuchElementException_ , but callers must be prepared to
             | get a _RuntimeException_ that wraps any other kind of
             | exception.
             | 
             | That makes the implementation adhere to the interface, but
             | only in name.
             | 
             | (Note that the writers of the interface, the implementation
             | of the disk-backed container, and the user of that
             | container often all will be different teams at different
             | organizations)
             | 
             | So, why declare that typed exception, in the first place?
        
               | 9dev wrote:
               | But isn't the actual advantage here that it gives me the
               | ability to handle that error case specifically? I mean
               | stuff like IO errors can happen everywhere at any time
               | anyway, but if I need to do something specifically if
               | there is no such element, isn't that a plus? Otherwise,
               | I'd have no chance to handle that error if I can do so.
        
               | MBCook wrote:
               | Handling there is probably the right thing to do.
               | 
               | I think the argument is that there is probably a better
               | way of expressing that in code than exceptions. Perhaps
               | returning an Either<Data, ErrorCode> kind of value.
               | 
               | There's also an argument that that's not truly an
               | exception because it should be expected that an I/O error
               | is a reasonable possibility. Things like OutOfMemory or
               | VirtualMachineError are more what they may be thinking
               | exceptions should be saved for. Truly unexpected things.
        
         | rahkiin wrote:
         | Also nice: a throw in Swift is like a return! It is setting the
         | exception in a specific register and then returns. The 'throws'
         | signature indicates to the caller two things: the user needs to
         | use some exception handling, and the callsite needs to handle
         | the special return register in case it is filled with an
         | exception
         | 
         | This way you do not need to do expensive stack walking
        
         | dwaite wrote:
         | Typed throws (of errors, swift does not support exceptions) are
         | for pretty specific usage scenarios - there was a years-long
         | recurring discussion around adding them to the language (which
         | I was an opponent).
         | 
         | They are useful within your own module, e.g. when they aren't
         | part of an exported API, as an alternative to other error
         | handling methods.
         | 
         | You can support them in generic utility methods like map,
         | because you will just rethrow the typed throw.
         | 
         | Otherwise, it is meant for systems programming or use in
         | embedded environments - basically the 'leaf code' that doesn't
         | have upstream variability or independent upstream dependencies.
         | It is meant to indicate things like 'errno' in a POSIX API. I
         | actually feel it is a poor functional fit there; the valid
         | error results differ both across errno-setting functions and
         | across UNIX implementations of a particular function. The
         | symbolic assignment (semantic error code to errno numeric
         | value) is also variable across implementations. However, this
         | allows the compiler to know errors are an int-sized stack type,
         | vs a witness of a heap-allocated object.
         | 
         | To look at it differently, errors are meant either for recovery
         | or for indicating a general failure for code to attempt to
         | clean up from. Once you delegate an error to other code by
         | rethrowing it, you no longer are conveying proper knowledge for
         | recovery - so there's no purpose to having it be typed; all you
         | can really do is try to fail gracefully.
         | 
         | A specific example - something like Swift Data is a poor fit
         | for typed throws, because the database layer itself is
         | adaptable. My Application doesn't know whether a failure is due
         | to the local disk being out of space or a transient network
         | issue connecting to a remote server, and attempting to recover
         | from these in my application code is a pretty bad pattern
         | because I'm baking in assumptions about a particular
         | configuration of Swift Data across my application code (or into
         | applications which use my module).
         | 
         | My opposition to the feature is that it is never required (you
         | can indicate failure in the return signature via something like
         | Result), adds overall language complexity, and is likely to be
         | misused in cases where it doesn't provide value - it just adds
         | ABI complexity over simply documenting expected Error types.
        
         | jayd16 wrote:
         | >What's the reason behind their prevalence in almost every
         | language?
         | 
         | Unexpected errors are inescapable when you consider OOMs and
         | other things so its almost required to support that. Ending
         | scopes and bubbling up an error without a ton of unwrap
         | boilerplate is actually really compelling.
        
         | troupo wrote:
         | > What's the reason behind their prevalence in almost every
         | language?
         | 
         | More often than not it's not the caller that is responsible for
         | handling errors/exceptions.
         | 
         | When you force the caller to take care of every single error,
         | you end up with unreadable boilerplate code which hides the
         | actual logic. There's a reason why Rust ended up with the `?`
         | syntax sugar.
         | 
         | On top of that exceptions _will_ occur. You can 't pretend they
         | won't and kill the app if they do. Again, even Rust and Go
         | ended up adding handlers for their brain-dead panics.
         | 
         | Exceptions (when wielded correctly) end up simplifying your
         | program. You develop for the happy path (mostly), and let code
         | at the higher level of hierarchy make decisions about unhappy
         | paths. That's how you get Erlang's supervision trees (https://e
         | rlang.org/documentation/doc-4.9.1/doc/design_princi...)
        
           | eikenberry wrote:
           | IMO the problem with tools that are great "when used
           | correctly" is that if they don't force that "correctly" part
           | or the feature works in such a way that people just fall into
           | correctly due to path-of-least-resistance, then people don't
           | use it correctly. This, again IMO, is why people have
           | problems with Exceptions. It is that they don't have these
           | qualities and they are almost universally used incorrectly...
           | thus the new languages have eschewed them much like they
           | eschewed heavy handed OO abstractions. They were tried and
           | found to be lacking for their intended purpose and
           | alternatives are being tried.
        
             | MBCook wrote:
             | Right, that's the Java issue. It becomes very easy to just
             | add "throws Exception" all over the place to silence
             | warnings when things start to get tricky.
             | 
             | Because Swift forces the keyword 'try' (or a variant)
             | before calling code that may throw it ends up being a lot
             | of work to try to avoid the issue just by making everything
             | throw. It's much easier to do the correct thing and just
             | handle the error in a smart place.
        
           | MBCook wrote:
           | I'm not familiar with Rust, but Swift has three options and
           | it sounds like it may be similar.
           | 
           | In Swift code that throws when called must have "try" in
           | front of it, making it really obvious where that's going on.
           | Your three options:
           | 
           | try - calls the code and either returns the like normal or an
           | error that you're forced to handle in a catch.
           | 
           | try? - calls the code and returns the value or nil (null
           | value) if an error is thrown.
           | 
           | try! - calls the code and returns the value. If the function
           | throws an error your app panics.
           | 
           | It's quite nice. You can choose to handle the error when you
           | need to. If you don't really care about the specifics of the
           | error and just want to treat any kind as a failure try?
           | cleans up your code.
           | 
           | And try! lets you avoid writing boilerplate when you know
           | it's impossible for the error to be thrown but the compiler
           | can't deduce that from the source alone.
        
         | nomel wrote:
         | An exception allows you to handle exceptional errors at any
         | level, without having to handle errors, or _write a single line
         | of code_ , at every other level. You can assume perfection, and
         | put that catch at the level of the abstraction where there's an
         | actual concern for the exceptional, keeping all the other code
         | simple.
         | 
         | I've always worked strongly in the physical world side of
         | software, like network stacks, test equipment, robots, etc, so
         | I can't see a sane alternative, that doesn't involve in
         | increasing the LOC by 20%, and being error prone (it's trivial
         | to accidentally eat an exception if you're relying on returned
         | values).
         | 
         | If you don't want to bubble up the exception to the user,
         | that's trivial too. You just catch it at whatever level you
         | choose, handle whatever, then return something nice.
        
       | betimsl wrote:
       | Everything old is new again.
        
       ___________________________________________________________________
       (page generated 2024-06-11 23:01 UTC)