←back to thread

Go channels are bad

(www.jtolds.com)
298 points jtolds | 7 comments | | HN request time: 0s | source | bottom
Show context
david-given ◴[] No.11211075[source]
I've always really, really liked Ada's rendezvous-based concurrency.

There's more to it than I can really describe here, but in effect it allows you to treat a thread as an object with methods; calling a method on the object sends a message to the thread. The thread's main code can, at any point, block and wait for a message, or combination of messages.

The handling code looks like this:

    ...some code...
    accept DoSomething(value: in out integer) do
      ...some code here...
    end
    ...some more code...
That causes the thread to block and wait for the message. When the message is sent, the caller blocks, the receiver runs the handler, then the caller resumes.

The beauty here is that inside the message handler, you know that the caller is blocked... which means it's safe to pass parameters by pointer[]. Everywhere the parameter's in scope, the parameter is safe to use. The type system won't let the thread store the pointer anywhere without copying the contents first, so you get zero-copy messaging and* it's failsafe.

You can also do really cool stuff with timeouts, guards, automatic thread termination, etc. Here's a simple multithreaded queue (syntax and logic not guaranteed, it's been a while):

    loop
      select
        when usage < capacity =>
          accept Push(value: in integer) do
            data[usage] := value;
            usage := usage + 1;
          end;
      or
        when usage > 0 =>
          accept Pop(value: out integer) do
            usage := usage - 1;
            value := data[usage];
          end;
      or
        terminate;
      end select;
    end loop;
Multithreaded! Blocks the client automatically if they pop while the queue's empty or push while it's full! Automatically terminates the thread when the last connection goes away and the thread leaves scope! Thread safe! Readable!

I'd love to be able to do this in a more mainstream language.

[*] This is a simplification. Ada's pointers are not like other language's pointers.

replies(3): >>11211589 #>>11211639 #>>11211817 #
msbarnett ◴[] No.11211817[source]
The older I get, the clearer it is that Ada was the answer to the last 2 decades worth of problems (fast, able to go low-level when you need to, very type safe, easy concurrency primitives) and we all just ignored it because it wasn't fashionable.
replies(3): >>11212044 #>>11212403 #>>11212438 #
1. david-given ◴[] No.11212403[source]
I would love to have a modernised Ada. With case sensitivity. And garbage collection (a lot of the language semantics are obviously intended to be based around having a garbage collector. I'm very surprised that it never seemed to get one). And a less wacky OO system (invisible syntax, ugh).

But those are quibbles, and at it's heart it's still an excellent, excellent language. And there are Ada compilers in Debian, it's still being maintained, it compiles really quickly into excellent code, it interoperates beautifully with C...

replies(2): >>11212489 #>>11212538 #
2. com2kid ◴[] No.11212489[source]
> And a less wacky OO system (invisible syntax, ugh).

Didn't Ada 2005 fix the OO system to give it the CLASS syntax everyone is used to?

Ada's usual syntax and declaring class inheritance are isomorphic with each other, the transformation a compilers does are the same, but non-JS programmers are used to class inheritance syntax.

I've always wondered if JS programmers would actually pick up on Ada's object system faster, just because they wouldn't mind the lack of an explicit inherits quite so much.

As for GC, I thought it was optional in Ada, just never implemented. For most of Ada's target audience though, heap allocators are already verboten, so GC isn't needed. :)

I'd really like some of Rust's ownership semantics along with Ada's already well developed feature set. Pointer ownership is still a gnarly problem, I don't recall what, if anything, Ada does to help out with it.

3. msbarnett ◴[] No.11212538[source]
I don't mind the lack of GC. Storage pools are reminiscent of Objective-C's autorelease pools, which I've always thought were a very nice way of handling a group of objects' lifetimes.

> And a less wacky OO system (invisible syntax, ugh).

Not sure what you mean by this one?

> And there are Ada compilers in Debian, it's still being maintained, it compiles really quickly into excellent code, it interoperates beautifully with C...

I came across http://www.getadanow.com the other day. Really easy way to get Ada going on OS X, too.

replies(1): >>11213130 #
4. david-given ◴[] No.11213130[source]
Okay, so I can't duplicate the exact OO syntax issues I was having before. But, from memory, I was finding that by putting the wrong kind of statement between the type definition and the method declaration, I could thoroughly upset the compiler --- there was invisible syntax connecting the two together, and if you I put the wrong thing in between, then things stopped working.

But as I can't duplicate it it's entirely possible I was just hallucinating.

In general I find the OO syntax desperately confusing. It feels like it's been shoehorned in on top of the existing record and procedure syntax, and it's never clear exactly what anything does. e.g. you need to suffix the object type with 'class in methods in order to make them non dispatching, but you need to suffix the object type with 'class in variable types if you want them to dynamically dispatch? That's not a happy choice.

(Case in point: I've just spent 20 minutes trying to refresh my memory by making this code snippet work. And failing. What am I doing wrong? http://ideone.com/6iPdYF)

Incidentally, re getadanow.com: that's really nice! And it's not pointing at the Adacore compilers, either; beware of these, as their standard library is GPL, not LGPL, which means you can't distribute binaries built with them. (The standard GNAT version is fine.)

replies(1): >>11213728 #
5. msbarnett ◴[] No.11213728{3}[source]
> But as I can't duplicate it it's entirely possible I was just hallucinating.

There's a thing where if you declare a type A, and then a derived type B, methods on A have to be declared before type B gets declared, because B's declaration "freezes" A. I think it's mostly a single-pass optimization that might have made sense 20 years ago but is meaningless in an era of gigabytes of RAM.

> (Case in point: I've just spent 20 minutes trying to refresh my memory by making this code snippet work. And failing. What am I doing wrong? http://ideone.com/6iPdYF)

The specific error message is: you declared a package, which is basically a header in C parlance. You declare signatures in it, not method bodies. Method bodies go in package bodies. You were conflating the package and the package body.

And then from line 19 onwards you were using the package name where you wanted to be using a class name. I cleaned it up a bit and made it work: https://gist.github.com/mbarnett/9c6701fe74524a6df522

replies(1): >>11215827 #
6. david-given ◴[] No.11215827{4}[source]
> There's a thing where if you declare a type A, and then a derived type B, methods on A have to be declared before type B gets declared...

Yes, that sounds very familiar.

> The specific error message is: you declared a package, which is basically a header in C parlance.

Oh, FFS. That snippet is not, in fact, pointing at the piece of code I was actually asking about --- ideone changed it when I wasn't looking. The one you saw is unfinished and broken.

This one is the one I was meaning: http://ideone.com/skZRIb

The .Foo selector isn't found; changing it to Foo(object) reports that apparently Foo isn't a dispatching method on MyClass1... which makes no sense, because this is the same code as you had. My suspicion is that there's something magic about declaring classes in packages?

replies(1): >>11218311 #
7. msbarnett ◴[] No.11218311{5}[source]
> which makes no sense, because this is the same code as you had. My suspicion is that there's something magic about declaring classes in packages?

Yeah.

Dispatching methods on a type consist of the type's "primitive operations". The Ada 95 Rationale spells it out: "Just as in Ada 83, derived types inherit the operations which "belong" to the parent type - these are called primitive operations in Ada 95. User-written subprograms are classed as primitive operations if they are declared in the same package specification as the type and have the type as parameter or result."

It seems like a wart that you're not in an "anonymous" package in situations like your example, but I also guess it probably doesn't come up much in "real" programs.