Notes on structured concurrency, or: Go statement considered harmful - <AUTHOR> https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ <comment><USER2> What.

This article proposes a "nursery", which is just a wrapped sync.WaitGroup/pthread_join/futures::future::join_all/a reactor that waits for all tasks to terminate/etc.

It then uses an exception-like model for error propagation to "solve" error handling (which is fairly easy to handle with a channel).

The construct is a decently usable, already applied tool to handle a set of problems, but the article takes the issue way out of proportions and overhypes the proprosed solution. The "with" example for benefits to not having a "go" statement seem rather bogus, especially seeing that such RAII constructs do not exist in Go (no destructors, remember?).

Trying to claim that "go" is as terrible as the original "goto" is ignorance of the original problems. Bad use of goto can be a nightmare to track (as the author tried to illustrate), but goroutines do not jump around, they _branch_ from the main goroutine, following normal control flow from there. They are easy to follow, and the language is designed so that you can throw around with them and forget them without them causing you problems.

Also, this article is comparing a list of concurrency constructs and one parallism construct (pthread_create—threading.Thread doesn't count as parallism due to GIL) to _callbacks_ , something which have nothing to do with concurrency _at all_. Very odd.

<comment><USER3> I thought the article's comparison of go routines to goto was fair, in the context made: when calling a function in a language that allows go-routine like things (including pthread_create), one can't know if background tasks will be spawned. This is similar in the free-form time of unrestricted goto that one could not know if control flow really would return to the point lexically after the function call. His proposed abstraction does allow one to look after a nursery block and know whether or not background tasks are still running.

<comment><USER2> Knowing whether a background task has spawned is very different from not being able to follow the control flow (goto potentially jumping to an entirely different function body).

Now, while Go is designed mostly for you to not _care_ about goroutines, there are some corner cases where one must know if a _resource_ is used by anything, such as the chase of when you wish to close a file handle.

However, I'd argue that this is not related to go's concurrency model and the presence of background tasks at all. It's related to object lifetimes. This should be made clear at API surfaces.

A language solution to this would be Rust's lifetimes, not a new concurrency model.

<comment><USER4> But this concept does also solve that problem. Maybe more simply.

Rust is renowned for being a total headache with borrowing and lifetimes. Nurseries might be a simpler solution for this.

As you say, a corner case. But a common one...

<comment><USER5> Nurseries and lifetimes are orthogonal concepts. In fact, Rust _already has_ a "nursery", except it's generally called a scoped threadpool. They use lifetimes to ensure the threads can reference stuff on the stack without copying it. So a "nursery" is basically just a scoped threadpool with a 'static lifetime.

<comment><USER6> I don't think there's any reason a nursery has to have a static lifetime.

<comment><USER7> The description of the nursery is you can pass it around wherever you like. More generally, this concept seems to have been designed in a language without lifetimes. You obviously can have a scoped threadpool without a 'static lifetime, but if you want to pass it around to arbitrary locations then you do.

<comment><USER8> I think the whole point of the nursery's design is that you can create and destroy them during the life of your program, and that destroying them forms a barrier where the owner of the nursery waits for its children to finish. Having a nursery with a static lifetime in Rust would therefore be pointless, as it would live until the end of the program.

<comment><USER7> I think you misunderstand. I'm talking about writing something like `ScopedThreadpool<'static>`, which means any external data referenced by the threadpool must have a static lifetime (or rather, it means the ScopedThreadpool cannot reference anything on the stack, because that would prevent you from e.g. returning it to your caller or passing it to another thread). The ScopedThreadpool itself can be created and destroyed whenever.

</comment></comment></comment></comment></comment></comment></comment></comment></comment><comment><USER9> The author appears to have reinvented Communicating Sequential Processes (CSP).

[http://www.usingcsp.com/cspbook.pdf](http://www.usingcsp.com/cspbook.pdf)

The (simplified, and as I understand it) gist of concurrency in CSP is that the program is expressed as a series of parallel (PAR) and sequential (SEQ) operations.

Everything in a PAR block will run in parallel and all their outputs will be collected and fed as the input to the next SEQ. Everything in a SEQ will run sequentially as a pipeline until the next PAR. Every PAR must follow a SEQ and vice versa, as two PARS or SEQS next to each other will simply coalesce.

eg.

        PAR
          longCall1
          longCall2
          longCall3
        SEQ
          reduceAllThreeResults
          doSomethingWithTheReducedResult
        PAR
          nextParallelOp1
          nextParallelOp2

etc.

<comment><AUTHOR> This misses the point of the article. It's short-sighted to say that he's reinvented CSP given that the entire concurrency model in Go is based around CSP, and the author is already aware of it. The article is more related to RAII, scope, lifetimes, etc. than any model of concurrency.

<comment><USER10> Channels are lifted from CSP, but PAR is obviously missing.

<comment><USER11> par is "select" effectively. While that isn't 100% the case, it is true.

<comment><USER10> Huh? I thought "select" was "ALT"??

<comment><USER12> 'go' is PAR with different syntax (Everything after the 'go' line is implicitly one branch of the PAR)

'select' is ALT

<comment><USER10> Hmmm...as far as I understood the FA, the point is exactly that 'go' is _unlike_ PAR in important respects.

'go' is more like a goto, the new goroutine is spawned, without any scoping. PAR opens a scope where all the contained routines are executed in parallel, but all of these must have terminated before the statement after the PAR is executed.

?

</comment></comment></comment></comment></comment></comment></comment><comment><USER13> This is usually why I end up using [https://godoc.org/golang.org/x/sync/errgroup](https://godoc.org/golang.org/x/sync/errgroup) instead of straight go statements, as it addresses some of the cancellation and error propogation issues. When I think of my use of naked go statements, it's usually for periodic tasks; having something similarly structured for them would be a clear win to me (though potentially the impact is less significant, as it's less painful to write a well formed periodic task using the go statement and context).

<comment><USER14> ErrGroup is nice, but it was created before contexts existed, and doesn't have support for cancellation. I have a bounded worker pool executor that handles cancellation that I'm currently extracting from a private project; shout out if interested.

<comment><USER13> The errgroup I linked has a single constructor `WithContext(ctx.Context) (*Group, context.Context)`. I think you might be thinking of the stdlib's sync.ErrGroup :)

<comment><USER14> Oops, I didn't realize that. Thanks. Mine implementation supports a bounded (max concurrency) mode, though!

</comment></comment></comment></comment><comment><USER15> This addresses the wrong problem. The real issue is control over data shared between threads, not control flow.

C/POSIX type threads have no language support for indicating what data is shared and which locks protect which data. That's a common cause of trouble. The big question in shared memory concurrency is "who locks what". Most of the bugs in concurrent programs come from ambiguities over that question.

Early attempts to deal with this at the language level included Modula's "monitors", the "rendezvous" in Ada, and Java "synchronized" classes. These all bound the data and its lock together. Rust's locking system does this, and is probably the most successful one so far. (Yes, the functional crowd has their own approaches.)

Go talked a lot about controlling shared memory use. The trouble with goroutines, as Go programmers found out the hard way, was that the "share by communicating, not by sharing" line was bogus. Even the original examples had shared data. But the language didn't provide much support for controlling that sharing.

Python is basically at the C level of sharing control over data, except that the Global Interpreter Lock keeps the low-level data structures from breaking. This prevents Python programs from doing much with multi-core CPUs. Since this is just another thread library for Python, it has the same limitations.

Real concurrency in Python with disjoint data, and without launching a heavy- weight subprocess, would be a big win. But this isn't it.

<comment><USER16> Almost all attempts at CSP-style programming in the end resorted to sharing data to get a little bit better performance. I wonder whether we shouldn't have used a bit of speedup that Moore's law gave us to cover that cost and be done with all the shared state headaches.

<comment><USER17> Except shared data doesn't give you a little bit better performance, it gives you _massively_ better performance. Or, in some cases, it's the only way to get usable performance at all.

Now what you could do is break objects down into annotated types. Consider immutable vs. mutable in combination with thread-unsafe, thread-compatible, and thread-safe. Immutable data that's not thread-unsafe you can share freely across threads, all is well. L2/L3 caches are happy. Mutable that's thread- safe can similarly be shared at will. Then you can force that thread- compatible objects be wrapped & accessed only from a Mutex or transfered between threads as part of a move operation.

Rust gives you the tools to do all of this, and indeed does some of it, but as part of the steep learning curve of the ownership model.

<comment><USER15> I proposed something like that for Python in 2010.[1] Immutable objects could be shared. Mutable objects had to either be unshared, or a subclass of an object that enforced synchronization.

Python's Little Tin God didn't like it. Mostly because I proposed to freeze the code of the program once the second thread started. That takes away much of the dynamism he insists on.

Might be worth looking at again. The separation of data into immutable, unshared, or synchronized is mainstream now.

[1] [http://animats.com/papers/languages/pythonconcurrency.html](http://animats.com/papers/languages/pythonconcurrency.html)

<comment><USER17> Python seems like an odd place to try and shove this into. Both due to its heavy object mutability & dynamic nature in combination with the GIL making heavy threading of python code a waste of everyone's time anyway.

There aren't many languages with the concept of ownership or moving at all, and trying to retro-fit that is probably going to be not a good experience for anyone involved.

Rust is largely there, in yet another thing it does well, but if you don't want that something like C++ would be probably a better place to try it. There you at least have move & ownership as a language concept already.

<comment><USER15> When I wrote that, Rust didn't exist, C++ didn't use move semantics much, and Python was more important than it is now.

</comment></comment></comment></comment></comment></comment><comment><AUTHOR> I thought the title was kinda clickbaity, but it turned out to be a great article. Also the comparison to goto really effectively conveyed the point he was trying to make. I have two questions though:

* Does anything else like this currently exist (other than the Trio library he mentions), which shows that it's a superior paradigm in practice?

* What are the cons to this approach? Why not do it?

<comment><USER18> I don't think its a superior paradigm, just a different one.

I only see his nursery as being useful when you really want your async tasks to complete before the function in which they were dispatched returns. That's far from covering every use case of concurrency!

A lot of the value of concurrency is in background operations. These simply can't be tied to the duration of a function call on the dispatch thread. Doing so will literally kill any advantages of concurrency in the first place, might as well just block directly. (This is especially true of apps modelled as an update loop, you definitely don't want to block that loop.)

I can think of very few places where I'd actually want a nursery, and even in those cases I'd rather use the promises or fork&join already available.

<comment><USER19> > A lot of the value of concurrency is in background operations. These simply > can't be tied to the duration of a function call on the dispatch thread.

If your background operation has those characteristics, maybe it is better to spawn a new process for it. Why a thread?

<comment><USER18> A process has much higher overhead (both in time and space), more complex communications and is not always possible in the first place. It also adds complexity to the build pipeline. These quickly adds up to being not worth it over a task or thread.

You wouldn't spawn a process for the rendering/input/physics/network/job/loader threads of a game engine for one. You can't spawn processes from a web app either.

Threads are actually pretty darn simple when you have either immutable data or uniqueness constraints. Deterministic parallelism is also very powerful while preventing all sorts of nasty bugs.

<comment><USER19> > A process has much higher overhead (both in time and space), more complex > communications and is not always possible in the first place.

This can change if OSes start optimizing for the proposed convention.

> It also adds complexity to the build pipeline.

At least in C/C++ it doesn't. But again, this is also easily solvable if we want to.

> You wouldn't spawn a process for the > rendering/input/physics/network/job/loader threads of a game engine for one.

Why not? In my mind, if you need 2-way communication between 2 threads, they should be siblings. If they need one way communication, there should be a parent-child structure. If they need no/minimal communication, they are better off as processes. I don't know which categories each of the threads you named fall into. I realize that this approach will require extensive redesign of existing software, very much like the elimination of goto required.

> You can't spawn processes from a web app either.

No reason for it to stay that way.

> Threads are actually pretty darn simple when you have either immutable data > or uniqueness constraints. Deterministic parallelism is also very powerful > while preventing all sorts of nasty bugs.

I won't pretend to know all those words :P I just think the article's proposal has some merit and we should consider it. >

<comment><USER18> > This can change if OSes start optimizing for the proposed convention.

What conventions? Its not realistic to assume the world will change to fit your views of software :p

> At least in C/C++ it doesn't.

Sure does; it adds more build targets, gets you to maintain shared code across executables, and plan deployment for multiple executables instead of one. Thats all before even coding the support for that.

> Why not?

Because these depend on shared memory and ownership transfer for performance; you'll drastically drop performance just for the sake of isolation.

> No reason for it to stay that way.

I will literally stop using the web if pages can spawn processes :)

> I won't pretend to know all those words :P

Hehe, basically immutability makes it so nobody can mutate data, thus making it safe to be shared across threads. Uniqueness will transfer ownership such that only one thread has references to a mutable piece of memory at any time. Deterministic parallelism means its impossible to have race conditions or deadlocks.

> I just think the article's proposal has some merit and we should consider > it.

Agreed, I'm still having a hard time seeing it however :p

</comment></comment></comment></comment></comment></comment><comment><USER20> It's amazing how many people managed to skim through the post, and hammer on their own preconceptions and facile counter-arguments, for things that are all addressed in the argument.

And that's for a very well written post, that tries to address all common issues.

And yet, people manage to get it wrong, or write facile responses like "re- implementing the fork/join".

Not to mention missing the whole nuance of what the author is talking about, which is not about novelty of a feature, but about what it allows us (and even more so, what it constraints us).

It's like as if people being shown for loops and structured programming in the 60s responded with "this proposal just reinvents gotos". Or worse, that "this is more restrictive that gotos".

Yes, the author knows about the Erlang's model. He writes about it in the post, and about how you can use his proposal to do something similar.

Yes, the author knows about Rust's model. In fact Graydon Hoare, the creator of Rust (now working at Apple on Swift), has read the post's initial draft and gave his comments to the author.

<comment><USER8> If the author knows about Rust, it's a little odd that he didn't mention scoped-threadpool, which is pretty close to this idea:

[http://kimundi.github.io/scoped-threadpool- rs/scoped_threadp...](http://kimundi.github.io/scoped-threadpool- rs/scoped_threadpool/struct.Pool.html)

EDIT: He compares to Rust in a comment on the Reddit thread:

[https://www.reddit.com/r/programming/comments/8es8x3/notes_o...](https://www.reddit.com/r/programming/comments/8es8x3/notes_on_structured_concurrency_or_go_statement/dxxucgi/?st=jggijgyw&sh=3a0dced9)

As an aside, HN users tend to sneer at Reddit, but this is another case where the discussion on Reddit is better than the one here.

</comment></comment><comment><USER21> > As a result, every mainstream concurrency framework I know of simply gives > up. If an error occurs in a background task, and you don't handle it > manually, then the runtime just... drops it on the floor and crosses its > fingers that it wasn't too important.

You ought to look into Erlang and Elixir on the BEAM vm/runtime. It's arguably the best example of this kind of concurrency (greenthreading, async) done properly with regards to error handling.

I don't write Elixir or Erlang, but I believe this process is managed by the supervisor. You can select various behaviours for when a process crashes or errors out[1]. For instance, you can have a process simply restart after it crashes. Combined with a fail-fast mentality, this produces remarkably fault tolerant and long lived applications.

[1]: [http://erlang.org/doc/design_principles/sup_princ.html](http://erlang.org/doc/design_principles/sup_princ.html)

<comment><USER22> Somewhat agree.

Supervision trees (or sync.WaitGroup in Go) are good tools for achieving the same end result. But it isn't as semantically protected as the article states. In Trio, the property is given by the programs scope.

However, if process creation/termination follows the scoping rules of the program, I have a hunch you run into situations where certain things are not only hard, but outright impossible to express.

Now, the author gambles that this is a good thing and we will eventually find good structural solutions to all the problems. I, on the other hand, is a bit more pessimistic because it has been tried before and found to be lacking.

I wonder how Trio handles error propagation.

<comment><USER23> Not sure what you mean by semantically protected? OTP supervisor trees enforce the constraints that are specified in the supervisor's start function. It's semantics are defined by the OTP library and function scopes. As you mention there are times to break out of the strict (supervisor) pattern if needed/wanted and start processes manually. Otherwise the default behaviors provide a nice set of limited behaviors that have proved useful across a broad spectrum of situations.

Really what TFA's discussing seems much more akin to OTP than raw Erlang. Or more specifically a subset of it. Occasionally I wish there were a few more OTP supervisor behaviors but nothing that's a show stopper. Not familiar with Go's WaitGroup but I haven't seen it used much in code I've read.

</comment></comment></comment><comment><USER24> This is very very long to explain a very simple pair of ideas:

1) You should be able to declare scoped blocks that mandate execution of all tasks started in that block ends when the scope ends.

2) This is fundamentally superior to all other forms of concurrency.

I get it; this is basically what async/await gets you, but conceptually you can spawn parallel tasks inside an awaited block, and know, absolutely that all of those tasks are resolved when the await resolves.

(this is distinct from a normal awaited block which will execute tasks inside it sequentially, awaiting each one in turn).

...seems like an interesting (and novel) idea to me, but I flat our reject (2) as ridiculous.

Parallel programming _is hard_ , but the approach from rust, to give you formal verification, instead of arbitrarily throwing away useful tools seems much more realistic to me.

<comment><USER25> The article makes a strong case. Even if rust _allows_ formal verification, not all programmers use it, and if you use their library, you don't know if it's been verified.

Take it back to the goto analogy: can you formally verify goto? Yes. Does that mean it's good to include as a language primitive? No.

You are basically taking an entire argument, ignoring its merits, and saying "but you can do it another way". You are exactly right, but you haven't rebutted the fact that structured concurrency is philosophically superior.

I love rust and the community; they will get to the truth of this argument eventually. But I suspect the truth is that "njs was right", and I hope the it's sooner rather than later.

<comment><USER26> It's not only that Rust allows formal verification, it's that it does it by default, writing unsafe Rust code for no serious reason is fundamentally frowned upon, and even then there is a serious effort to bring some tooling to bring more confidence even on "unsafe" Rust code.

> But I suspect the truth is that "njs was right", and I hope the it's sooner > rather than later.

I'm not sure. The problem can and has been solved without the manually "pass the nursery object around" ""escape hatch"" for the _general case_ of threads (because no: having the execution of spawning functions delayed by the lifetime of thread is arguably reasonable is _some_ cases, but certainly not the _general case_ of what threads are useful and used for)

It is still useful for tons of existing languages as a pattern anyway, but only _if_ the use cases are suitable.

But the author is focused on a narrow use case of threads (and a narrow subset of the problems they introduce), and present their solution as a general truth and new fundamental control structure of computing, independent of already existing, in production, and arguably better solutions; and independent of analyzing the new problems their silver bullet introduces.

<comment><USER27> "Formal verification" for Rust code -- does that actually exist yet, or do people just hope/assume that the language will be proven consistent and that awesome theorem-checking tools will emerge eventually?

I agree that Rust has a great model that seems to lead to very solid code, but "formal verification" is a high bar to clear.

<comment><USER28> Some people are actively working on formal verification for Rust. The RustBelt project is the first that jumps to mind. A paper and some discussion here: [https://news.ycombinator.com/item?id=16302530](https://news.ycombinator.com/item?id=16302530)

</comment></comment></comment></comment></comment><comment><USER29> I LOVE Go's concurrency model, but this article has won me over (pending some experimentation anyway).

If you just skimmed, this is actually worth a careful read. The parallels between "go" and "goto" are explained very clearly, and you get some awesome Dijkstra quotes to boot!

<comment><USER30> I think people are missing the merits of what the author said in their defensiveness of their existing approaches, which is a bit sad.

<comment><USER31> The article had a whole section on how any challenges to existing paradigms and tools will be met with fierce opposition. The comments here are an excellent illustration!

</comment></comment></comment><comment><USER32> Article persuasive _prima facie_ and arguments plausible. I've had to reinvent a structured method of managing threads a number of times, unfortunately.

Author is a PhD student, which bodes well for not reinventing wheels dumbly. Therefore, I look forward to the lit review of other concurrency & parallelism work through the last 40 years, which this writeup notably lacks (author mentions his stack of papers to review).

 _Your ideas are intriguing to me and I wish to subscribe to your newsletter._

ed: author has phd, not is a student.

<comment><USER7> > _Author is a PhD student, which bodes well for not reinventing wheels > dumbly._

Except that's exactly what the author did. They just reinvented scoped threadpools.

<comment><USER20> In the sense that a car is a reinvention of a horse buggy.

<comment><USER7> In what way? Nurseries sound like they're exactly the same thing as a Rust scoped threadpool with a 'static lifetime. Not some sort of super modernization like you're suggesting.

</comment></comment></comment></comment><comment><USER33> I have yet to see any of these problems in core.async (Clojure's version of goroutines). core.async has enabled fantastic programming abstractions like async pipelines using transducers and "socket select" type programming. Perhaps functional programming is the solution to solve concurrency issues.

<comment><USER18> Clojure has HUGE advantages over Go in the first place, that helps immensely in making concurrency sane and safe. Most of the goodness in Clojure is emergent of its design, very few languages have that property.

The simple fact that Clojure can implement goroutines as a library shows how flexible the language is. Then there's immutability and a strong focus on simplicity among others.

</comment></comment><comment><USER34> What timing!

My project is currently struggling with how to migrate to Python async. The biggest challenge is the place where async and sync interface.

Just the other day, my colleague was wondering out loud about the possibility of using a context manager to constrain the scope of async. This is it. This is exactly what we were looking for.

</comment><comment><USER30> So, my initial thought when reading was "yeah, it's async/await". But it's subtly different than that though.

You're free to spawn parallel tasks in async/await -- you just use Promise.all or Future.sequence, or whatever your language provides to compose them into a larger awaitable.

Nurseries seem to go a step beyond this by reifying the scheduling scope as the eponymous nursery object. This means that you have a new choice when the continuation of your async task happens: a nursery pass in from some ancestor of the call tree. My gut says that this offers similar power as problematic fire-and-forget async tasks, but takes away the ability to truly forget about them.

My guess is that, in practice, you end up with some root level nursery in your call stack to account for this. But account for it you must! And while the overwhelming sentiment in these comments is pretty dismissive, I'd caution against downplaying the significance of this. It's basically like checked exceptions or monadic error handling.

I also think about how this maps to task or IO monad models of concurrency. It seems like there's an inversion of control. Rather than returning the reification of a task to be scheduled later, the task takes the reification of a runtime, upon which to schedule itself. I'm not sure what the ramifications of this are. Maybe it would help with the virality of async return values [1], but at the cost of the virality of nursery arguments.

Lastly, one thing this article nails is the power of being able to reason about continuation of control flow. Whether or not nurseries have merit as a novel construct, this article still has a lot of educational use by making this argument very clearly. Even if the author is wrong about nurseries being "the best", it sets a compelling standard that all control mechanisms--async or not--should have to explain themselves against.

I do have a couple questions:

\- In the real world, would library APIs begin to get clogged with the need for a nursery on which to run an async task? Think async logging or analytics libraries.

\- Would usages similar to long-running tasks that receive async messages be compatible? I'm thinking of usages of the actor model or channel model that implement dynamic work queues.

\- Does this increase the hazard presented by non-halting async tasks?

[1] [http://journal.stuffwithstuff.com/2015/02/01/what-color- is-y...](http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your- function/)

</comment><comment><USER35> This other post from the same blog gives some more concrete background on why this sort of structured concurrency is a good idea: [https://vorpus.org/blog/some-thoughts-on-asynchronous-api- de...](https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a- post-asyncawait-world/)

</comment><comment><USER36> "our call stack has become a tree"

This is a really useful property to have and reason about.

Instead of several independent coroutines with arbitrarily overlapping lifetimes, we can now think of all coroutines as organized in a _single hierarchy_ with properly nested lifetimes.

The function call stack becomes a call tree - each branch is a concurrent execution.

</comment><comment><USER37> Lately, I am of the idea that the real problem with how we do concurrency is that we have yet to figure out a way to do it without first-class procedures. When we spawn a thread, even in a low language such as C, we use something to the effect of:

        launch_thread(function, perhaps, some, initial, data);

The trouble with this approach to concurrency is twofold:

(0) It forces a hierarchical structure where one continuation of the branching point is deemed the "parent" and the others are deemed the "children". In particular, if the forking procedure was called by another, only the "parent" continuation may return to the caller. This is unnatural and unnecessarily limiting. Even if you have valid reasons to guarantee that only one continuation will yield control back to the caller (e.g., to enforce linear usage of the caller's resources), the responsibility to yield back to the caller is in itself as a resource like any other, whose usage can be "negotiated" between the continuations.

(1) It brings the complication of first-class procedures when it is often not needed. From a low-level, operational point of view, all you need is the ability to jump to two (or more) places at once, i.e., a multigoto. There is no reason to require each continuation to have a separate lexical scope, which, in my example above, one has to work around by passing "perhaps some local data" to `launch_Thread`. There is also no reason to make "children" continuations first-class objects. If you need to pass around the procedure used to launch a thread between very remote parts of your program, chances are your program's design is completely broken anyway. These things distract the programmer from the central problem in concurrent programming, namely, how to coordinate resource usage by continuations.

</comment><comment><USER38> The main culprit to me in Golang seems to be channels, not goroutines. If your workflow essentially is defined by a mesh of channels and goroutines, it's hard to reason or understand.

I have no direct practical knowledge of Golang, but working on a large application that used BlockingQueue for concurrent communication and one which extensively used services buses for communication - both were hard to understand and reason about flow.

After some years with Scala Futures I'd say they work well and reason well. They can be seen as normal function calls returning Future instead of another 'container'.

They reflect the black box mentioned in the article, with one way in and one way out (e.g. when a method returns Future[_]).

The point about error handling: We use Option,Seq.empty on read error handling, Validation on create/write and Either on side effects (like sending mail).

(yes, they are still leaky abstractions e.g. when debugging, but work fine most of the time)

<comment><USER30> I think the article's point is that with Future's you can still pretty easily invoke a Future-returning function and forget to return its value, ending up with what you might call an orphan continuation.

<comment><USER38> If the future has a side effect I'm not concerned with, like sending a mail, I can't see the problem?

<comment><USER30> The problem is much like the author said -- it's easy to have errors disappear into the ether in a way that is much less likely in synchronous logic. Also, if those side-effects matter, it's easy to make faulty assumptions about time ordering.

The most obvious situation to me is in the way asynchrony exists in front-end programming and how this affects testability. If you can't actually know when a process (like an animation) ends, you can't accurately test.

In general, my experience has been that reification of abstract things often presents benefits in the long run. Reification of functions admits a whole host of techniques. Reification of classes facilitates metaprogramming. Reification of in-flight processes as promises helps with being able to compose and abstract over them. Nurseries seem like reficiation of an finite execution context.

<comment><USER38> I'm not following, how could an error with Future[Either[A,B]] disappear compared to synchronous logic of Either[A,B]? Our code base is the same for sync and async logic and error handling.

One thing that doesn't work is Anders Hejlsbergs method of letting unchecked exceptions bubble up, but exceptions haven't been a good idea for business code anyway.

</comment></comment></comment></comment></comment><comment><USER39> Interesting ideas, and well-worded.

You could achieve something similar in JavaScript with Promise.all() and await:

      await Promise.all([
        asyncFunc1(),
        asyncFunc2(),
        asyncFunc3()
      ])

Of course, that's not language-level and the point seemed to focus more on eliminating traditional branching than just adding another way to do it.

<comment><USER40> That's exactly what I was thinking. Some of us are probably are using this type of control flow. For me a common scenario is something like

        const promises = files.map(readFileAsync); // "nursery"
        const fileContents = await Promise.all(promises); // "with"

I generally agree with his premise that it sucks having to figure out if a function is concurrent or not; i.e., does it return a value or a Promise/Future. I'm not sure if his solution solves that particular issue though, unless it's handled automatically in his "nursery.start_soon" function.

</comment></comment><comment><USER41> Worst title ever. I never complain about this stuff, but can someone please change it? You think it's going to be some analysis about Go, but instead it's someone pushing their library.

<comment><USER42> I think this article is a lot more than just "someone pushing their library"

It is well organized, written and specific about it's claims.

</comment></comment><comment><USER43> Didn't expect to say this, but the article is completely right! This is obviously the right way to write concurrent programs. Kudos for writing this.

One question though. The first part of the article says that "onclick" handlers should be replaced with nurseries as well. But I don't see how. Can someone explain?

<comment><USER44> I suspect that if you were to try to do this in real code, something like an onclick handler would have to be run in a nursery scoped at the page level, by the code running the page. An onclick handler on its own accord can't run itself in the correct nursery. It's like the network handling case there, where the nursery's lifetime is either unbounded, or tied to the lifetime of the OS process, depending on how you want to look at it. Functions don't always return within a program, or at least, don't always return excepting maybe a last cleanup as the program terminates.

<comment><USER45> Maybe a subtle difference here is that a page doesn't get to block on anything before it gets closed.

So how is creating a nursery that matches the lifetime and visibility of the page different from not using a nursery at all?

<comment><USER44> "Maybe a subtle difference here is that a page doesn't get to block on anything before it gets closed."

In real browsers, the UI act of closing a tab and the completion of cleaning up all of its resources are already clearly separated. I can see that when I terminate a browser with a lot of tabs and the main window has entirely closed while the browser runs at 100% CPU for quite a few more seconds. Plus a browser can probably hard-kill running Javascript code with some reasonable effectiveness. (Not sure. Asynchronous exceptions are very, very hard.)

So I don't think that's a disqualifier.

"So how is creating a nursery that matches the lifetime and visibility of the page different from not using a nursery at all?"

Well, as I've said in one of the reddit conversations, bear in mind the entire purpose here is not to enable something that was previously impossible, but to _constrain_ us from using primitives in their most powerful form. The big thing is that if you had a nursery-based system, and the page had a nursery associated with it, and the page's render routine returned, you'd _know_ that all the page threads must necessarily be terminated. You can't know that with the same confidence now, because the programming style does not permit that level of confidence by construction.

Browsers are a bit of a pathological case on a lot of levels, though, and probably not a great mental example, because you can assume a high degree of competence, concern, and skill in the people programming the browser, and they do things like use static analysis all the time and even in the "worst" cases, develop entire new programming languages to write browsers in. So you are probably justified in saying "But jerf, I'm pretty confident the browsers are already cleaning up their stuff without this stuff." The real question is, how does this look for Joe Programmer and his ability to work in the domain of multithreaded programming, which is well known and widely acknowledged to be very difficult, by constraining what mistakes he can make?

One of the other angles you can look at this with is, if I have two pieces of correct code and I compose them together, are they still correct? The nursery system says that with my nursery, I can call other code that uses them, and the composition attained over that function call is also correct and does not leak resources. We do not get that guarantee with some other primitives. We do with others. There's a lot of experimentation still going on in this field. I'm not saying this approach is guaranteed to be correct, but it's one of the more plausible claims to being a primitive as basic as "if" is relative to "goto". Compare with Software Transactional Memory, which as nice as it may be, is wildly more complicated than "if" no matter how you slice it.

</comment></comment></comment></comment><comment><USER46> I was underwhelmed by reading this (maybe due to a clickbaity title and IMO sketchy extension of badness from goto to the go statement).

That said, the article has good technical content. It proposed a new concurrency library with interesting properties. Concurrency comes with additional cost. The library proposes a paradigm to minimize certain costs and should provide punchy examples of how things can be done simply and efficiently with it.

But instead it is picking shallow fights with the go statement (does the author know about the "sync" package and WaitGroup)? Overall I found the advocacy section _WAY_ too long. Use most of that real estate to show goodness of your library, not on trying to punch holes in the competitors. My 2c.

</comment><comment><USER47> [http://rvirding.blogspot.com/2008/01/virdings-first-rule- of-...](http://rvirding.blogspot.com/2008/01/virdings-first-rule-of- programming.html)

</comment><comment><USER48> I'm wondering if some promise/future systems already provide a similar guarantee. The main useful property of the nursery system is that a function's signature indicates whether that function leaves a background task running. If you can guarantee that promises/futures are dropped if they go out of scope, then you get a similar guarantee. Similarly, if you have a system where promises don't actual run their background parts unless someone is waiting for them, then there are no leaky background tasks.

</comment><comment><USER49> I'm really growing fond of the async/await abstraction. How do I get this in more C-like languages like Go, Rust, or C++? (I have a bunch of C++ code that I want to call via C ABIs.)

I'm intrigued by libdill but it's mysterious enough that I'm scared to include it in my project -- I don't want to risk getting sidetracked by having to debug my concurrency primitives.

<comment><USER50> Rust is working on exactly this, targeting stabilization later this year: [http://aturon.github.io/2018/04/24/async- borrowing/](http://aturon.github.io/2018/04/24/async-borrowing/)

<comment><USER51> The RFC is in its final comment period: [https://github.com/rust- lang/rfcs/pull/2394#issuecomment-383...](https://github.com/rust- lang/rfcs/pull/2394#issuecomment-383773009)

Very excited!

</comment></comment></comment><comment><USER52> I found this pretty fascinating and I'm looking forward to hearing the theoreticians chew it over.

A question I had was with the API that's been chosen. The `nursery` is chosen as the reified object, and a function `start_soon` is exposed on it. Perhaps in other parts of the library there are other methods exposed on `nursery`? If not, in some languages it seems like the `start_soon` method itself would make more sense as the thing to expose. In use, it might do like this:

        ...
        nusery {
          (go) in
          go { this_runs_concurrently_in_the_nursery }
          go { this_also_runs_concurrently_in_nursery }
          // make a regular function call passing it the nursery's `go`
          some_func(go)
        }
        ...

And elsewhere:

        ...
        func some_func(go) {
          do_something()
          go {
            nursery {
              // This nursery is within the outer one.
              (go2) in
              go2 { do_stuff }
              go2 { do_more_stuff }
            }
          }
        }
        ...

</comment><comment><USER40> > whenever you call a function, it might or might not spawn some background > task. The function seemed to return, but is it still running in the > background? There's no way to know without reading all its source code, > transitively. When will it finish? Hard to say.

This reminds of me "colored functions" (red vs blue) where it becomes imperative to know if a function you are calling returns a value or a Future/Promise.

Some languages allow annotating a function to indicate as such so the IDE can help. His particular solution he presents actually doesn't address this question: Is your function sync or async? You still have to know when calling a function if it's async and needs to be in a nursery or not.

Should a programming language abstract away whether a function is async or not? async/await is a step forward (C#/JS) but it still requires knowing if the child function is async or not.

</comment><comment><USER53> I found it an interesting idea and writeup. It'd be good to see what could be done in a language that implemented this - concurrency only allowed in the context of nurseries. On the downside, though, I think there's a lot of concurrency patterns that could only be implemented by creating a nursery near the top level of the application and passing it around all over the place, thus getting you pretty much right back where you started.

Want a web app that sets up some long-running thing to run in the background while the request returns quickly? Well then you're going to need a nursery above the level of the request which is still available to every request. I don't see what that gives you above conventional threading. Oh, and you'd also need to implement your own runner in a thread to have a task failure not bring down the whole application.

<comment><USER54> > I think there's a lot of concurrency patterns that could only be implemented > by creating a nursery near the top level

Yes, the same thing is true of goto and other control flow patterns. Patterns change to match the tools available.

</comment></comment><comment><USER55> Is incredible how easy is to miss the point of this.

Let me try with something else.

Imagine you have try/catch/finally BUT NOT AS CONTROL FLOW CONSTRUCS but "just api calls".

So, you language need to be used like:

        foo()
        exceptions.try{
        	bar()
        	this.catch{

        	}
        }

It means, you need to remember to ALWAYS REMEBER to "close" the start of the call.

Imagine how bad this could be. If only "try/catch" was as with "IF/ELSE/ENDIF" so you not do something stupid like:

        foo()
        exceptions.catch{
        	what?()
        }
        bar()

</comment><comment><USER56> His note on resource cleanup doesn't really make sense -- or, rather, it only makes sense if you're using python's solutions for it. What about something like, say, c++ where you have a smart pointer that keeps track of the number of references to it, to store your file pointer? Then when you pass that to your concurrent function, it won't get cleaned up, until that other thread finishes, then that reference to it will be lost and it'll clean itself up.

</comment><comment><USER57> Coming from an occam/transputer background (in the 1980s) I felt goroutines were too low level. Luckily its trivial to add a PAR-like construct via sync.WaitGroup (e.g. github.com/atrn/par). That said Go's channels are far easier to work with - buffering and multi-producer/-consumers being very common needs which, in occam, you implement yourself. The lack of guards in Go's ALT (select) is a shame and the nil channel hack is just that, a hack.

</comment><comment><USER58> I know people already said it, but causality, resource cleanup and error handling are all solved with Actor model in a more general, more flexible and more reliable way than nurseries.

If you think reasoning about concurrency is hard, try testing and modelling it, especially for something distributed. This is where naive ideas about concurrency should start to fail and a need in solid foundation arise.

</comment><comment><USER59> There are other approaches. State machines, reactors are often used in low level layers and the Actor model for some high level design.

<comment><USER60> When you add in queues for passing things back and forth between the async functions, I think this boils down to an Actor model. The nursery would be equivalent to supervisors in Erlang.

</comment></comment><comment><USER61> The author might want to take a look at Habanero Java, which has all of this and more.

[https://wiki.rice.edu/confluence/display/HABANERO/Habanero-J...](https://wiki.rice.edu/confluence/display/HABANERO/Habanero- Java)

</comment><comment><USER62> go statement is just another form of explicit process creation, fork/join pattern. What the author suggested is just similar to cobegin/coend -- implicit process creation.

cobegin/coend are limited to properly nested graphs, however fork/join can express arbitrary functional parallelism (any process flow graph) [1]

Yes, for graceful error handling it needs to form some sort of process tree or ATC(Asynchronous transfer of control), which is implemented in Erlang/OTP and ada programming.

[1] [http://www.ics.uci.edu/~dillenco/compsci143a/notes/ch02.pdf](http://www.ics.uci.edu/~dillenco/compsci143a/notes/ch02.pdf)

</comment><comment><USER63> I don't like the term "nursery" (maybe "highway" or "complex" or... something else) but this seems to be a good design change, unless I'm missing something

<comment><USER40> I believe it's used because it keeps track of "children", or child functions spawned by the current function. Without knowing that background however, it's not immediately obviously to someone that hasn't heard the term what it means.

As others have mentioned, reusing the "await" keyword could cover a lot of these nursery scenarios.

</comment></comment><comment><USER64> How is this different than calling join(), after spawn()?

<comment><USER44> How is if different that just using goto to go to either the true or false clause? It isn't about what you can do, it's about what you _can 't_ do if you use this mechanism (this restricts where you can call spawn and join) and the guarantees you can then build on top of that.

<comment><USER64> build what on top of it? If i want a blocking thread i would just call a function.. something something goto something isn't a valid argument that applies to everything. The other way to propagate errors up is chaining promises.

<comment><USER19> > If i want a blocking thread i would just call a function

That is not what this is though.

</comment></comment></comment></comment><comment><USER65> Well they lost me at the callbacks... if go statements are harmful and callbacks are not then I rather prefer the harmful way.

<comment><USER66> You might want to read further. He didn't claim that callbacks are not harmful. In fact he suggested that they suffer from the same problems and he is using "go statements" to encompass all of the forms of concurrency handling.

</comment></comment><comment><USER67> How did someone manage to write a post that long discussing Go and it's concurrency model and not once mention channels?

</comment><comment><USER68> That's pretty cool! Thanks for enriching my day and good luck with your framework!

</comment><comment><USER69> A point I believe the author has missed: Goroutines aren't simply forked functions, but are abstracted as independent, autonomous processes. Now, that doesn't mean 'go' is a sufficient tool to reason about goroutines, but this may also not be as useful a solution as he touts it to be.

<comment><USER31> I don't think that's really relevant to his point. His argument is about control flow, rather than capabilities. The library he has built is designed to provide any functionality that didn't previously exist, but rather to make code that accomplishes the same tasks as before easier to reason about and somewhat safer to write.

An implementation of this pattern could handle the spawning of functions however it wants and the language supports (as independent processes, threads, with an event loop, etc.).

</comment></comment><comment><USER70> Hmm.. Looks like the author is reinventing fork-join style parallelism...

<comment><USER20> Looks like people skim articles and apply anything they already know and recognize to explicitly more evolved ideas...

</comment></comment><comment><USER71> This is absurd. The only thing "go" has in common with "goto" is the first two letters.

</comment><comment><USER19> I think this has a lot of merit. And the analogy to goto is very apt and deep. I suspect that 20 years later all our concurrency interfaces may well look like this.

However, the momentum of today's programming community may be too great to surmount. When goto was criticized, there were fewer people to convince to give up on it. Now, there are orders of magnitude more devs. And all of them are comfortable in the current way of doing things.</comment>


Ask HN: Archive of well-designed PSDs? - <AUTHOR>

I'm a coder. I can design decently, but I'm terrible at getting things looking beautiful.
I've seen Dribbble (which host mostly images, not psd's) and sites like PremiumPixel which provide a few PSD's. But what I'm looking for is a large, tagged archive of "open-source" PSD's.
Does this exist? If not, would you use it? <comment><USER2> <http://365psd.com/>

</comment><comment><USER3> There are a few, including 365psd, DesignMoo,and PremiumPixel. I've found myself glancing at them a couple times, but when it came to putting that design into web design, it became a useless service for me (now I rarely make a PSD).

</comment><comment><USER4> I don't know of one. It would compete with "stock" image services that sell PSDs, AIs, etc. If you can find 10k+ free, "worth owning" PSDs that people give you royalty-free, the right person could definitely make some money with it.</comment>


Ideas I'd Like To Work On - <AUTHOR> http://zacharyburt.com/2011/05/ideas-id-like-to-work-on/?utm_source=twitterfeed&utm_medium=twitter <comment><USER2> I'd like to work on bio engineering. I think it's the new hacking. And the _real physical world_ is the new internet.

<comment><USER3> Check out Stanislaw Burzynski and his son, who claim to cure cancer by deactivating genes that cause cancer and activating the ones that kill it, on a patient-by-patient basis with "FDA approved gene-targeted medicines" <http://youtu.be/tXf7r4VBDgk> <http://www.burzynskiclinic.com/>

Blows me away.

</comment></comment><comment><USER4> I do feel a bit like kicking myself for not seeing that bitcoin would end up where it is today. I first heard about bitcoin on HN when they were worth around .01 each, and could be generated fairly easily by simply running the mining software in the background. I was so busy spotting the flaw in the electricity cost creation argument I missed the fact it wouldn't matter, because the algorithm gets harder so only "professional" miners likely end up generating the majority of new coins, and also that there is a cap at 21 million, so generating coins was planned to stop anyway. (<http://news.ycombinator.com/item?id=1655202>)

I was looking at bitcoins as they might compare to an established currency model, like dollars, where the rate of creation has to be controlled smartly (not that I'm saying it is) and indefinitely. Satisfied I had spotted a money creation flaw preventing bitcoins becoming a dominant new currency I dismissed them quickly, never noticing the other aspects of brilliance it has.

I still see problems with bitcoin becoming a fully fledged sustained and mature currency. Some of these have been articulated here on HN (notably by jerf). However, I believe bitcoins still have developmental stages to go through before failing, and because of this I actually still see upside for them as a speculative (still risky) investment. I doubt I would do anything with them now, but if I had got in at .01 cent, now that would be different.

<comment><USER5> I just found out about bitcoins last weekend, and ran the bitcoin miner, and then found out that it will take my 2.4GHz dual-core laptop 10 years to generate a bitcoin at the currently-established difficulty (without using my GPU, presumably).

<comment><USER6> I know, right? Now that everyone's excited about Bitcoin, the exciting part is over. Kind of a bummer.

I see a unique opportunity here for a company with an existing virtual goods economy or something similar such as Listia.

They create a new genesis block and new Bitcoin-protocol clients and publicly announce they're going to turn the spigot on (announce the genesis block) a few days later. The twist being that they will accept the new chain's currency within their existing economy.

They not only get a lot of publicity but effectively become a central bank for their forked currency, which allows them to compete with Classic Bitcoin via the par value they establish.

I haven't thought this through, it's just a wild idea. What are the flaws in this plan? Would this just actually destroy value? Is this the expected long- run for Bitcoin anyway?

</comment></comment></comment><comment><USER7> As an aside, thanks for putting a name to a phenomenon I've never had a proper name for: survivorship bias. I had a "eureka" moment reading that, I've searched several times and never found the real name.

Wiki: <http://en.wikipedia.org/wiki/Survivorship_bias>

<comment><AUTHOR> [http://www.amazon.com/Black-Swan-Impact-Highly- Improbable/dp...](http://www.amazon.com/Black-Swan-Impact-Highly- Improbable/dp/1400063515/) have a field day

</comment></comment><comment><USER8> I'm very excited about Bitcoin too, it feels like a renaissance for almost every vertical. Especially since there's so much low-hanging fruit right now and there's still a disconnect between these units of cryptocurrency and real hard cash in people's perception. You can get people used to doing things with Bitcoins what they normally wouldn't do with "real money".

<comment><USER3> ... like investing in startups.

<comment><USER9> Yes, you can invest in bitcoin microcorporations that started their IPO on the Global Bitcoin Stock Exchange.

However, these corporations are not legally declared or chartered by the state. They only exists in the mind of some bitcoiners.

For some reason, Bitcoin unlock the mind of hackers and encourage them to go wild and do crazy things like creating companies that exists only on...cipherspace.

</comment></comment></comment><comment><USER10> I definitely like the theme of what you wish to work on (helping people). I myself would like to work on a project like your Friend Finder (really anything to create interactions between people), as I'm always trying to find like-minded people nearby. I have ideas if you ever wish to team up.

<comment><AUTHOR> please send me an email

</comment></comment><comment><USER11> I'd like to build something on bitcoins too. I have several ideas (billing, twitter-donation, p2p exchange, services exchange like LETS).

I can bring on board bitcoin knowledge (i co-founded a popular mining pool) and scala + lift programming.

<comment><USER12> Lift is nice, but does it actually scale to more than one machine? If not, then it wouldn't be something I would use for a startup.

<comment><USER11> It scales with sticky sessions on load balancer

</comment></comment></comment><comment><USER3> Bitcoin is firing me up.

A www.secondmarket.com for Bitcoin? Drop me a line.

<comment><USER13> Do you have any plans for how to handle Bitcoin eventually being made illegal in the US?

Or the other side of the coin --- Bitcoin being devalued when the "miners" sell off thousands of Bitcoins (to cash out)?

It's kind of interesting to think about the implications.

<comment><USER3> It's most likely going to become illegal when and if people start getting burnt (a few bad eggs spoil the basket), but we're hackers and we don't see obstacles, only speed humps.

<comment><USER14> Or if the powers that be start feeling threatened by it.

Personally I think it is too early to tell what is going to happen. I would love to see it succeed and become something that makes the world a better place.

<comment><USER3> As soon as tax departments feel they are missing out, and people are evading paying tax, that's really when the excrement will make physical contact with the electric powered oscillating air current distribution device.

</comment></comment></comment></comment></comment><comment><USER15> What's Bitcoin?

<comment><USER8> Start here: <http://www.weusecoins.com/>

If you want more of a third-party historical overview, Wikipedia has a decent article: <http://en.wikipedia.org/wiki/Bitcoin>

In short, it's a decentralized digital currency built on cryptography.

<comment><USER15> So the gist of it is that I'll buy (or pay for) things online -- products and services -- using Bitcoins instead of my debit or credit card?

<comment><USER8> Doesn't even have to be online, Bitcoins also work really well peer to peer (unlike credit cards), and things like NFC-capable phones can take advantage of that in-person someday soon.

It's a completely independent currency, so products and services must begin to accept Bitcoin before you can use it to pay for things. That is, it's not interchangeable with debit or credit cards, so you'll need to acquire Bitcoins before you can do any of that (by buying them or earning them or mining them).

On the upside, it's much easier to send and receive Bitcoin payments than it is with credit cards. There are no "merchant accounts", nothing you need to fill out, no barrier to entry. Just run the Bitcoin daemon, it even has a convenient JSON-RPC protocol.</comment></comment></comment></comment>


Randy Suess, computer bulletin board inventor, has died - <AUTHOR> https://www.nytimes.com/2019/12/20/technology/randy-suess-dead.html <comment><USER2> Wow, I didn't get online until a got a 300 baud modem around 1981 but I remember dialing into CBBS once. I remember because I was on the west coast and it was in Chicago so I just dialed in to say I'd connected. To minimize the cost I got my dad's stopwatch out to ensure I hung up at 59 seconds exactly.

What I find fascinating now is that I was able to connect, read the greeting, look through the main menu and leave a note in whatever counted as the 'guest book' \- all in 59 seconds at a connection speed a decent typist could beat.

Yesterday, I turned on my XBox One for the first time in several months so a visiting pre-teen could entertain themselves while his parents chatted. It took more than 20 minutes to atone for the crime of not turning it on for a while. First, it demanded a 'mandatory OS upgrade' followed by a reboot, then that I re-login and verify an MSFT account I never use (despite only wanting to run one game from a DVD-ROM already in the drive). Then the game itself wouldn't run until we completed a mandatory update download even though it worked fine last time it ran and we didn't try to do anything online.

BTW, not just bagging on XB. My PS4 pulls the same shit. If one of the next gen consoles coming soon advertised a "Just fucking load this media and work" mode, I'd choose it solely for that reason.

<comment><USER3> What I read out of this is that times were better when there were fewer bad actors. Every UX pain you mention is somewhat a fight against piracy and hacking.

<comment><USER2> > times were better when there were fewer bad actors.

At the meta-level, I don't think there are, per capita, more 'bad actors' in any broad consumer population today than there were 30 years ago. Most people are generally about the same morally and ethically in their day-to-day lives as we were back then. I think mass media and social media are incentivized to make things look worse today but the data doesn't support that view.

Media and software piracy are good examples. It's no harder today to pirate most music, movies or games than it was 20 years ago. Comparing the free Napster-to-Torrent options vs the paid-Spotify, Netflix, and Xbox/PS4 subscription services show consumers will pay a reasonable price for convenience. I would argue that anti-piracy efforts that make the user experience worse are shortsighted optimizations that are long-term foolish. In 2019 the recording industry set a new total revenue record thanks to the subscription model, finally surpassing the peak of the physical media era.

</comment></comment></comment><comment><USER4> Well, thank you a lot Randy! Great idea, good times...

In case this interests someone, I recommend you to check out Jason Scott's BBS Documentary [[http://www.bbsdocumentary.com/](http://www.bbsdocumentary.com/)], and while the DVDs sold out, it seems to be available on his youtube channel [[https://www.youtube.com/watch?v=Dddbe9OuJLU&list=PL7nj3G6Jpv...](https://www.youtube.com/watch?v=Dddbe9OuJLU&list=PL7nj3G6Jpv2G6Gp6NvN1kUtQuW8QshBWE)].

PS. adding a trailing dot after .com in the article URL saves you some trouble :)

<comment><USER5> I was going to recommend the same thing - great documentary. One of the places I learned about BBS's because I missed out (born too late). Also...I would recommend [[https://www.textfiles.com/](https://www.textfiles.com/)] to see some content from some bulletin boards

<comment><USER6> Jason Scott uploaded all the video he took trailing to places making the BBS Documentary to the Internet archive. There is more there.

</comment></comment></comment><comment><USER7> Thanks, Mr. Suess, for my introduction to online conversations and a profitable business model for a while.

My first introduction was to a BBS that was localized around Santa Cruz, CA - We even met up once in real life at the Scotts Valley Roller Rink. I think it was called Internet Cafe, although in retrospect that seems very vague as a name... My ISP shortly thereafter was Mtnweb... Which got bought by Got.Net, which is still in operation!

Then later I made decent revenue selling BBS systems based on WorldGroup to Builders Exchanges in the Bay Area. My wife and I would hand make the sign-up kits for contractors, complete with 3 1/2" discs and installation/connection instructions, sent in a nice little manila envelope.

I still have a few of the WorldGroup software box packages in my library...

My how times have changed. That was back in the period where I was _lucky_ if a vendor had a 1200 BAUD modem (or faster - Maybe even 9600!) hooked up to download drivers from - Assuming I could get through with no busy signal. Otherwise I had to wait 3-5 days for a drivers disk to arrive via postal mail.

Memories...

<comment><USER7> Hah! My wife just found a donations envelope that never got sent in addressed to Electric Cafe - THAT was the name of my first BBS... Internet Cafe did seem close but too generic.

</comment></comment><comment><USER8> I wouldn't be in this industry without stumbling into the BBS subculture. Thanks for everything!

</comment><comment><USER9> I was a Chinet subscriber in the 90s and early 2000s. For many years after I left UIUC it was one of the few places you could get a Usenet feed without belonging to a university or a company like AT&T. I met Randy at a number of parties and CBBS reunions.

He was a unique cat, and this NYTimes writeup was very nicely done.

Thank you, Randy. We'll miss you.

</comment><comment> a-dub ATH0

NO CARRIER

<comment><USER10> +++ Wait 1 second ATH

</comment></comment><comment><USER11> So sad to hear, as one who grew up with tech like this.

I actually had a short online chat with Ward Christiansen on CompuServe in.... 1985 to 1987ish time frame. He said he was surprised many people even remembered XMODEM.

<comment><USER12> Hah! XMODEM still shows up in a surprising number of places. Bootloaders and stuff use it for firmware recovery, although I think we're past the era where Cisco routers used it for OS upgrades.

</comment></comment><comment><USER13> RIP Mr. Suess, and I hope you're not cringing too much at "Mr. Suess and Mr. Christiansen built their electronic bulletin board using a personal computer called the S-100."

<comment><USER14> That sentence conveyed an important fact, but is missing a word or two for technical accuracy. Hopefully, after a long full life, he had much more important things to cringe about! Glad the Times published his obit even with this bit.

<comment><USER15> Here's a PDF of the 1978 _Byte_ with all the details.

[http://vintagecomputer.net/cisc367/byte%20nov%201978%20compu...](http://vintagecomputer.net/cisc367/byte%20nov%201978%20computerized%20BBS%20-%20ward%20christensen.pdf)

<comment><USER14> Super interesting primary source with the technical details and a bit about how they built the community. Thanks and merry christmas!

</comment></comment></comment></comment><comment><USER16> AT

OK

ATDT *70, 7778889999

Love those days. You always felt like you were in a secret society. You couldn't talk to anyone else about what you were up to because they wouldn't understand it.

Thanks Randy and Ward for a great gift. BBSs are what sparked my interest in computers in the first place. Thanks Jason Scott for the great documentary. Watched it several months ago and it brought me back. Wow those were good times.

</comment><comment><USER17> Mega RIP to the man responsible for a huge chunk of my childhood. I used to run Nucleus in Texas and Tennessee when I lived in those states. Was how I met my first friend in TN, when he logged into my then-blazing 9600 baud capable BBS, with LoRD and Sinbaud and The Pit and Baron Realms elite and Tradewars door games.

</comment><comment><USER18> I lived in Chicago and would log into CBBS, Ward and Randy's and The Ward Board all the time.

Thank you, Randy, for all the good times I had on those boards and the joy and excitement of discovering new things, even if was just to say hi to strangers with a similar interest.

</comment><comment><USER19> ICYMI: [https://paleotronic.com/2019/09/04/bbses-partying-online- lik...](https://paleotronic.com/2019/09/04/bbses-partying-online-like- its-1989/)

</comment><comment><USER20> I thought that Lee Felsenstein and the Community Memory project would count as the first?</comment>


Aviary: works like online Photoshop and more, plus remix other artists' work - <AUTHOR> http://aviary.com

<comment> old-gregg Jee. Online Photoshop. How about flying Photoshop or an underwater Photoshop? Every time I edit an image I think those would be so useful...

Seriously, has anyone, just one person in the world, ever had this thought when launching Photoshop: "ughh... I wish it was online so I could wait a bit longer..."

<comment><AUTHOR> 1) Aviary is a free service.

2) They offer more than just a photoshop clone. They've got four tools out right now (raster manipulation, procedural generation, palette creator, vector editor), and there's more on the way.

3) They store all the images online, which means you can see how every image gets made, which is a pretty useful thing for beginners.

4) They're making all the programs available as AIR applications, so they function on the desktop.

Jeez. Why not take a look at the thing before you try and rip it all apart.

<comment> old-gregg _Why not take a look at the thing before you try and rip it all apart._

Because my Photoshop files are 20-50MB (quite typical), and even worse, I produce about 2GB of RAW files with my Canon SLR per photo shoot, yet my upstream connection speed is about 45Kb/sec, and that's still probably in top 20% of US population (at most places I worked, connection was even worse). It's been like that for about 10 years and I see no signs of dramatic improvements within next 10 years.

Need I say more?

P.S. That example is a very typical Photoshop usage pattern, if you going to call something "Online Photoshop", you better have some idea what Photoshop is and what it does. If this was called a "Fun College Programming Project" I wouldn't be nearly as sarcastic.

 _They offer more than just a photoshop clone_

Oh, yeah, forgot to add. They're not "just a photoshop clone", they're nowhere near Photoshop 1.0 clone. I am getting quite intolerant to exaggerated claims, link-bait titles and buzzwordy discussions. Especially because after so many years of bullshitting there is only _one_ online app I can use: good old gmail. Just one. Plus time-wasters, of course.

<comment><AUTHOR> You're completely misrepresenting what I said. I'm not telling you "Hey, use this like you'd use Photoshop." I'm telling you "Click the link before you start trashing the thing." Aviary's people are pretty forthcoming about their product. They aim for hobbyists, not professionals. They've repeatedly said that they're _not_ a Photoshop clone and that they don't expect to ever _be_ as powerful as Photoshop. Rather, they aim to offer some of the most oft-used Photoshop techniques - including some that no other service online offers - and they do a damn good job at it.

When I say that something "works like Photoshop online," I expect people will understand that I mean "works with layers, allows for subtle image manipulation, churns out fairly high-quality images." Of course it's not literally Photoshop. But it does a lot of very neat stuff.

 _Oh, yeah, forgot to add. They're not "just a photoshop clone", they're nowhere near Photoshop 1.0 clone._

Again: way to utterly disregard what I said. They offer a lot of tools, and all their tools are damn impressive considering they're designed to run online. Yeah, you can ignore that and say that I'm a fuck-up because I used the word Photoshop when submitting. _Or,_ you could cut with the ranting, because on Hacker News people usually respond well to polite, civil discourse, and look at it for what it is, which is a pretty impressive offering. I think Peacock, the procedural generator, is actually _more_ impressive than Phoenix, which is the raster editor.

 _there is only_ one _online app I can use_

No. There's only one you _do_ use. If you need a calendar, there are impressive online alternatives. If you want a to-do list, the same. RSS reading: online alternatives that work better in some ways than desktop clients. Word processors and slide editors: Buzzword and 280Slides. Gmail's good, yeah, but frankly, Mail.app is better than it in every way that matters to me, and so your offering Gmail as the "only offline app" is just empty posturing.

How about we drop this: you can apologize for being a dick when you didn't have to be, and I'll apologize for using the name "Photoshop" in vain. Because this is a much better site than this snipefest would let on, and we could both be wasting our time much more efficiently. Peace?

<comment> old-gregg Unalone, I apologize for being a dick. Peace.

</comment></comment></comment></comment></comment><comment><USER2> Aviary has been around a while and its been mentioned several times, but it never seems to 'stick' with me. I always see it, remember its potential usefulness, and forget about it the next day.

<comment><USER3> I'm guessing that's a general recurring theme anytime a new technology arrives. Weren't Kleenexes originally for taking off make-up?

It'll be interesting to see where they take this service.</comment></comment>


Snowflake is the biggest software IPO - <AUTHOR> https://www.cnn.com/2020/09/16/investing/snowflake-ipo/index.html <comment><USER2> No comments here is interesting. Massive day for Snowflake and I'm excited. Fewer companies using Oracle products thanks to them.</comment>


Snips NLU, the Open Source Library Behind Snips Embedded Voice Platform - <AUTHOR> https://medium.com/snips-ai/an-introduction-to-snips-nlu-the-open-source-library-behind-snips-embedded-voice-platform-b12b1a60a41a <comment><AUTHOR> [https://github.com/snipsco/awesome-snips](https://github.com/snipsco/awesome- snips)

[http://linuxfr.org/news/snips-ouvre-sa-technologie- nlu](http://linuxfr.org/news/snips-ouvre-sa-technologie-nlu)</comment>


ImageNet Large Scale Visual Recognition Competition - <AUTHOR> http://image-net.org/challenges/LSVRC/2014/results <comment><USER2> I love looking at the results and seeing the annual improvement and breakout strategies. Deep learning has really come into its own during the last eight years.

People interested in learning about some of the techniques employed (CNNs and deep networks) are encouraged to take Geoff Hinton's Coursers course [1], which does a really good job of covering modern techniques from one of their pioneers.

[1] [https://www.coursera.org/course/neuralnets](https://www.coursera.org/course/neuralnets)

<comment><USER3> How much are results improving? What's the general long term trend?

<comment><USER4> The trend is depreciating returns with current architectures (after truly breakthrough results in the first place). It's a good time for companies to update their ML pipelines to use deep learning. Performance does seem to scale with hardware though, so as hardware improves (as well as the software), these models will see some more gains without any real technology change.

But I should also point out deep learning doesn't work for everything. Random forests or SVMs work about as well on certain (I would argue "more linear") datasets. Deep learning is for when RFs and SVMs aren't giving the results you need (or at all). It doesn't negate all things that came before, nor should it.

One thing to consider: As far as I can tell, there's an order of magnitude more people working on these types of problems with an order of magnitude more money being thrown at it and an order of magnitude increase in "collaborative efficiency" due to Internet and open source/research. I think breakthroughs are thought of best as a probability distribution, but I think the probability of further breakthrough successes has increased considerably in just the last few years.

There are reasons for optimism and pessimism, as usual.

</comment></comment></comment><comment><USER5> The winner got 6.7% in top-5 classification error. 2013 was 11%, 2012 was 15%. This may be _better_ than exponential, since there is an upper limit of 0% error, and every additional percentage point is harder than the last.

Exponential improvement in this area, and it's a pretty significant field that will affect everyone. The results will likely help improve neural networks in other areas as well, as NNs are extremely general purpose.

Here is an example from a system from 2012 in the same competition to give an example of what this is: [http://mappingignorance.org/fx/media/2013/04/Deep- learning-5...](http://mappingignorance.org/fx/media/2013/04/Deep- learning-5.png) A bunch of images were scraped from Flickr and then given a single label by a human. The computer gets 5 guesses to guess what label the human used. As you can see, even when the computer fails, it's guesses are usually semantically very good. This specific system got an error rate of 17%.

You can explore the images of ImageNet here: [http://image- net.org/explore](http://image-net.org/explore).

Here are some image recognition systems online you can play with:

* Toronto Deep Learning Demo: [http://deeplearning.cs.toronto.edu/](http://deeplearning.cs.toronto.edu/) * clarifai: [http://www.clarifai.com/](http://www.clarifai.com/) * AlchemyVision: [http://www.alchemyapi.com/products/demo/alchemyvision/](http://www.alchemyapi.com/products/demo/alchemyvision/)

Note these aren't related to the winner of this competition and probably are no where near as good. Just examples of this kind of system you can play with.

</comment><comment><USER6> Looks like BVLC's Caffe and R-CNN are getting a lot of mindshare. Also interesting to see that the creator of Caffe, Yangqing Jia, was on team GoogLeNet and didn't appear to have used either.

Edit: Last years results with no Caffe (just Decaf, its predecessor) [http://image-net.org/challenges/LSVRC/2013/results](http://image- net.org/challenges/LSVRC/2013/results)

<comment><USER4> Cuda convnet 2 was recently released, it's faster than caffe it seems. Caffe is very cool though, I've been playing with it a bit.

[https://code.google.com/p/cuda-convnet2/](https://code.google.com/p/cuda- convnet2/)

<comment><USER6> Hey Dave! (Craig here) nice, achieved 6x speed up on an 8 gpu box ([http://arxiv.org/pdf/1404.5997v2.pdf](http://arxiv.org/pdf/1404.5997v2.pdf)). Thanks for the pointer and hope to see you soon.

<comment><USER4> Hey Craig, nice to hear from you, I hope things are going well. That is a nice bump! The data and model parallelism they added is very cool. We'll be hosting a meetup soon, it's been a while, hope to see you there.

</comment></comment></comment></comment><comment><USER7> _Science is but a perversion of itself unless it has as its ultimate goal the betterment of humanity._ \- Nikola Tesla

Does this not nearly amount to "population-scale mass surveillance algorithms"? Do people not feel this is accelerating negative social impacts of technology?

Is it merely a coincidence that winning teams include many from countries criticized for their totalitarian social contracts: Hong Kong University of Science and Technology, National University of Singapore, Microsoft Research China, Southeast University (China), Chinese Academy of Sciences? There's also a presence from Holland.

Oh, and guess who won the category "with additional training data"? Google.

Come on people, we can do better than this! _SHAME SHAME SHAME._

<comment><USER5> Machine vision has _numerous_ applications, better surveillance being only one of them. Mainly self driving cars, and significantly better AI for robotics and some other automated systems.

The organizations you mention are just places that invest in or do a lot of research in AI.</comment></comment>


Show HN: Monitor your Raspberry Pi cluster with Prometheus metrics - <AUTHOR> https://blog.alexellis.io/prometheus-nodeexporter-rpi/ <comment><AUTHOR> It is important to monitor remote servers to make sure you are not running out of compute resource/memory or disk space. It's even more important on a Raspberry Pi cluster where your resources are especially limited. A full SD card can knock a Raspberry Pi off your network or prevent services from working.</comment>


Lessons I've Learned from Spinning Off Tech Companies - <AUTHOR> http://www.fastcompany.com/3059390/lessons-learned/3-lessons-ive-learned-from-spinning-off-the-companies?partner=rss <comment><USER2> Love that the stub in the url says "3-lessons" and the article title says "4 Lessons". I wonder which one is the afterthought...

<comment><USER3> Hah - yeah, I don't know what's up with that. They edited away from my original title as part of publishing.</comment></comment>


The Holmes of Programming - <AUTHOR> http://www.sbb.com.np/post/index.php?id=ZbtA5uVCOx

<comment><USER2> Archive.org: [https://web.archive.org/web/20150120162544/http://www.sbb.co...](https://web.archive.org/web/20150120162544/http://www.sbb.com.np/post/index.php?id=ZbtA5uVCOx)

<comment><USER3> That site throws at you more popups and popovers than the article was worth.

</comment></comment><comment><USER4> A presumably text document and "CPU limit reached". Something is seriously wrong with the typical blogging engines. Well, maybe static engines (like Jekyll, Hugo, yst, etc) will eventually take over those CPU-burners.

</comment><comment><USER5> "CPU Limit Reached" and without a single comment.

</comment><comment><USER6> Sherlock Holmes and the case of the runaway cpu cycles?

</comment><comment><USER7> mirror? cpu limit reached</comment>


Blind people 'read' letters that are traced on their brains with electricity - <AUTHOR> https://www.nature.com/articles/d41586-020-01421-6 <comment><AUTHOR> Original study: [https://www.sciencedirect.com/science/article/pii/S009286742...](https://www.sciencedirect.com/science/article/pii/S0092867420304967)

Abstract:

> A visual cortical prosthesis (VCP) has long been proposed as a strategy for > restoring useful vision to the blind, under the assumption that visual > percepts of small spots of light produced with electrical stimulation of > visual cortex (phosphenes) will combine into coherent percepts of visual > forms, like pixels on a video screen. We tested an alternative strategy in > which shapes were traced on the surface of visual cortex by stimulating > electrodes in dynamic sequence. In both sighted and blind participants, > dynamic stimulation enabled accurate recognition of letter shapes predicted > by the brain's spatial map of the visual world. Forms were presented and > recognized rapidly by blind participants, up to 86 forms per minute. These > findings demonstrate that a brain prosthetic can produce coherent percepts > of visual forms.

</comment><comment><USER2> This ted talk still has my mind blown. Not sure what the current state of the art is on this, but I find this amazing even today.

[https://www.ted.com/talks/sheila_nirenberg_a_prosthetic_eye_...](https://www.ted.com/talks/sheila_nirenberg_a_prosthetic_eye_to_treat_blindness)

<comment><USER3> Years ago I read about something like this. A blind man was even able to drive a car (I think it was the researcher's) around a parking lot. IIRC it had a limited framerate so he wouldn't be able to actually drive anywhere with it

I find the encoding of such a signal to be fascinating. Somewhere someone has mapped out the 'protocol' of the eye-brain interface! I would love to know more.

<comment><USER4> > Somewhere someone has mapped out the 'protocol' of the eye-brain interface!

I think it's more accurate to say they've tinkered their way into figuring out a few basic commands that can make the system predictably do a handful of things. Still no color, very limited resolution, and low framerate as you mentioned.

We've probably got over 99% more of that 'protocol' yet to discover. But at least we've got our foot in the door.

</comment></comment></comment><comment><USER5> Reminded me of this NPR story ([https://www.npr.org/programs/invisibilia/378577902/how-to- be...](https://www.npr.org/programs/invisibilia/378577902/how-to-become- batman)) in which they show that a blind person can technically see through hearing (echolocation), and then poise the (philosophical) question, what is seeing? Pretty fascinating.

<comment><USER6> I think seeing is pretty easy to define, it's the decoding of high-density two-dimensional information encoded in a wave. High-density differentiates it from feeling/smelling, and two-dimensional differentiates it from hearing/smelling. Is that definition missing anything relevant?

<comment><USER5> > Is that definition missing anything relevant?

Yes, wide-acceptance. Google the definition of seeing, does it resemble what you wrote?

Ask a lot of people what seeing is, would they give the same definition as yours?

Historically, seeing has been mostly regarded as what we are capable of doing with our eyes.

Also, in your definition, what is doing the decoding? Is the retina decoding, is the optic nerve decoding, are each individual cell decoding, are the individual neurons decoding? And what does it mean to decode?

Definitions require agreement, not just a seemingly factual description. At least if you want the definition to be accepted. Which is in the end what defines a definition.

</comment></comment></comment><comment><USER7> I am totally convinced that we will essentially invent telepathy within the next few decades.

We've already made strides with interacting with a computer via brain signals, if we can build simple text interfaces (like this) that interface directly with the brain we have all the pieces needed to silently send text messages using only thought.

<comment><USER8> My mind immediately goes to horrible implications like telepathic spam. Conversations getting interrupted with ads, screaming directly into your brain.

<comment><USER9> > Leela: Didn't you have ads in the 20th century? > > Fry: Well, sure, but not > in our dreams. Only on TV and radio. And in magazines and movies and at ball > games, on buses and milk cartons and T-shirts and bananas and written on the > sky. But not in dreams. No, sir-ee!

</comment></comment></comment><comment><USER10> Thank you for posting! Always fascinates me that we still find out how to brain works. Always get the feeling there is still so much left to discover about the human body :)

</comment><comment><USER11> Hook up some cameras to Neuralink[1] via the developer API (tongue in cheek) and boom, sight!

[1] [https://www.neuralink.com/](https://www.neuralink.com/)

<comment><USER12> Neutralink will be one of the most exciting things in the decades to come.

Plans are to install the first chips with over 1000 connectors within a year.

<comment><USER4> For those who haven't read it, the 'Wait But Why' extensive blogpost on Neuralink (warning, it's super long, I think it has enough of a wordcount to qualify as a novel if it was one) is probably the most fascinating thing I've read in the last 5 years:

[https://waitbutwhy.com/2017/04/neuralink.html](https://waitbutwhy.com/2017/04/neuralink.html)

A lot of it is speculation by necessity, and I'm sure some is outdated now, but it's also packed with facts and the reasoning seems sound. If anyone knows whether any of it has been debunked, I would be interested in that as well.

</comment></comment></comment><comment><USER13> Watch out for the overflow, Johnny Mnemonic.

<comment><USER14> All the best hackers are dolphins?

</comment></comment><comment><USER15> The actual journal article (linked in the article) has more info, it actually seems they did something interesting.

The calibration was a cool -

[https://doi.org/10.1016/j.cell.2020.04.033](https://doi.org/10.1016/j.cell.2020.04.033)

</comment><comment><USER16> I hope to see a non invasive way to do this soon, either via nano-bots or some form of magnetic stimulation. Years ago, our hard-disks got smaller, it is time for MRI and transcranial stimulation.

</comment><comment><USER17> That's a pretty high claim, shouldn't it be possible to demonstrate this effect on blindfolded sighted people ?

</comment><comment><USER18> things like this and BCIs are some of the most exciting tech products in existence, IMO

</comment><comment><USER19> So do sighted people...</comment>


Eve: Unbeatable computer developed by online community - <AUTHOR> http://eve-tech.com <comment><USER2> "THE SOLUTION

1\. Cut the middlemen out

We operate online. And we love it. There are no middlemen between us and our customers.

No ivory structures, no additional costs related. At Eve, world-class designers and seasoned manufacturing folk have come together, aspiring to deliver pure hardware experiences.

2\. Eve.Community

We operate online. And we love it. There are no middlemen between us and our customers.

No ivory structures, no additional costs related. At Eve, world-class designers and seasoned manufacturing folk have come together, aspiring to deliver pure hardware experiences."

That does not inspire confidence.

<comment><USER3> You are more then welcome to get big brand name and buy/pay yourself some confidence ;) Or you can simply pop in the Eve Community. Anyway, live long and prosper and greetings from the community :)</comment></comment>


How much energy goes into making a bottle of water? - <AUTHOR> http://www.physorg.com/news156506896.html

<comment><AUTHOR> A: 2000 times more than the energy required to produce tap water.

Another interesting fact from the article: ". . . world consumption of bottled water has increased by 70% since 2001 to 200 billion liters in 2007."</comment>


Man connected to IP address heist running for position at European IP org - <AUTHOR> https://mybroadband.co.za/news/internet/350973-man-connected-to-african-ip-address-heist-running-for-board-position-at-european-ip-address-organisation.html <comment><USER2> RIPE member / LIR voter here.

That idiot has disqualified himself very well: [https://www.ripe.net/participate/meetings/gm/meetings/may-20...](https://www.ripe.net/participate/meetings/gm/meetings/may-2020/candidate- biographies#elad_cohen)

<comment><USER3> Wow that is amazing! His proposal for "IPv4+" is... I have no words. Thanks for the laugh.

</comment></comment><comment><USER4> Author of the article here. If you have any questions, please feel free to ask.</comment>