On the proliferation of try (and, soon, await)

I'm a fan of try do { … }. For context, I've been lightly experimenting with a bash-like DSL for Swift and since invoking any command can fail, I've found myself creating workarounds to avoid needing to prefix every command with try. Would it make sense to pull this out into its own proposal? I can take a stab at a proposal, though I will likely omit the func f() throws try { syntax as the inline variant is a bit confusing due to the closeness of throws and try (@dabrahams' example includes a newline so is a bit more clear, but I think we should consider that the inline variant will also be valid). As for implementation, I'm still very much new to the Swift codebase, but can make an attempt if someone points me in the right direction.

7 Likes

Yes, I think it would make sense for this to be a specific proposal. Until and if that happens, it will just get endlessly batted around in forum threads ;-)

-Chris

1 Like

I of course plan to make it so… I'll have to implement it first, though.

2 Likes

Just wanted to express my support in favour of try do. I do prefer a more conservative approach, though; Without the throws try, like @George mentioned.

Just wanted to post some new insight from a project I'm working on. For various reasons, in this project, most of the code I have needs to be executed step-wise, in tiny increments, but with no parallelism. I actually have an execution object on which a mutating step() method is called repeatedly. You could easily imagine doing this with multiple execution objects in round-robin fashion to get a kind of concurrency-without-parallelism. It's classic inversion of control.

After trying some alternatives, I implemented it in continuation-passing style with a trampoline, which ends up introducing a lot of complexity and, of course, the classic pyramid-of-doom.

I would actually love to try using async/await for this project, but IIUC the parts are not yet available to use async without creating parallelism (I would love to be wrong about that, and if I am, I very much hope someone will correct me!)

In any case, it seems like almost a perfect application for async/await, and it would undeniably be an improvement over CPS. That said, because of the tiny increments I mentioned above, the code I'm trying to clean up would surely be littered with unhelpful awaits. For the purposes of this project, the fact that a function is going to be executed step-wise is something that needs to be said at most once, and the precise step divisions don't much matter.

Because I'm in CPS-land I've been thinking a lot about functional programming idioms and how monads with “do” notation would support the same kind of code transformation as I'd get with async/await, but with only one marking on the whole function where the transformation is applied, rather than a marking at each possible suspension point… which is what this thread is about.

fin.

4 Likes

If it's enough for your needs to have "everything" be single threaded, you could compile swift using the SWIFT_CONCURRENCY_COOPERATIVE_GLOBAL_EXECUTOR mode, which makes the entire concurrency runtime single-threaded.

Thanks, but a custom compiled swift is probably not going to be appropriate in this case; I have to work on this project with others, test it using GitHub actions, etc.

Yeah that’s not quite usable there then.

A silly solution might be to put “everything” onto @MainActor, or another global actor. This way there’s no parallel execution there. Though if you accidentally create a detached task, or a child task that would be parallel again.

What you actually need is custom executors and putting everything you do on the same serial executor. We don’t have this yet but it’s coming in future releases

4 Likes

Hmm, looking at that… forgive my ignorance;I can't tell whether it's possible, but I'd really rather not give up value semantics. Is there a way to put a struct and all its methods in @MainActor?

You can read the inference rules here: swift-evolution/nnnn-global-actors.md at global-actors · DougGregor/swift-evolution · GitHub

Basically you could annotate all your types/funcs with the main actor perhaps.

I suggest playing around with it a bit.

1 Like

Thanks! It seems like I should be able to use: “A non-actor type that conforms to a main-actor-qualified protocol in its primary definition infers actor isolation from that protocol,” assuming that “type” really means all types and not just classes as shown in the example.

I've long wanted to see throws! and throws? which would imply an implicit try! or try? respectively to any calls to the function. These could be overridden with an explicit try, try!, or try? when different behavior is desired.

3 Likes

Hmm, but what I don't see is how to get anything corresponding to the execution object described above. How would I take two such execution objects and alternately step() them?