Most active commenters
  • pjmlp(9)
  • kbolino(7)
  • jerf(6)
  • neonsunset(6)
  • Fire-Dragon-DoL(5)
  • hinkley(4)
  • limit499karma(4)
  • brightball(4)
  • throwawaymaths(4)
  • hosh(4)

175 points nateb2022 | 134 comments | | HN request time: 3.071s | source | bottom
1. Xeoncross ◴[] No.41521050[source]
> Zero dependencies

This is something seldom attempted, but I congratulate you. Go is one of a few languages that really is batteries-included. Just about anything you could need is included in the stdlib.

replies(4): >>41521157 #>>41521657 #>>41521695 #>>41521859 #
2. Xeoncross ◴[] No.41521090[source]
Never having worked with Erlang, my mind is telling me this might be an alternative for https://github.com/temporalio (database-backed functions) or AWS Step functions.
replies(1): >>41521544 #
3. seanw444 ◴[] No.41521157[source]
One of the reasons I prefer it over something like Rust for most projects. I don't have to waste time figuring out what third-party library is the defacto standard that I should be using.
replies(1): >>41521371 #
4. glutamate ◴[] No.41521214[source]
Love the idea, but i am having a hard time finding out what the code looks like. Where can i see the code for spawn, receive and send?

> ... a command-line utility designed to simplify the process of generating boilerplate code for your project based on the Ergo Framework

Why is there any boiler plate code at all? Why isn't hello world just a five line programme that spawns and sends hello world somewhere 5 times?

replies(3): >>41521800 #>>41522012 #>>41523043 #
5. that_guy_iain ◴[] No.41521221[source]
I love the name. It's just so good.
6. iknowstuff ◴[] No.41521371{3}[source]
It’s a tradeoff. Do you prefer to be stuck with bad built-in file and time APIs, or a robust ecosystem of external crates?

https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...

replies(6): >>41521608 #>>41521670 #>>41521708 #>>41521832 #>>41522235 #>>41523134 #
7. seneca ◴[] No.41521544[source]
They're not the same, exactly, but there are similarities. The actor framework isn't inherently durable workflows, for example. It does similarly distribute work though.
replies(1): >>41521715 #
8. hu3 ◴[] No.41521608{4}[source]
So your argument is that an ecosystem of external crates which are created and maintained mostly by individual contributors is on average better than the standard library backed and dogfooded by trillion dollar company and used by other giants of the industry? Can't say I agree.

Not to mention nothing prevents anyone from using or writing their own library only for the parts that need specialization. You're free to do that and many have.

And standard libraries can be versioned and deprecated too.

replies(2): >>41523867 #>>41564060 #
9. dlachausse ◴[] No.41521657[source]
Interestingly, Erlang is very batteries included as well. Probably even more so than Go in most cases.
replies(1): >>41521682 #
10. mijamo ◴[] No.41521670{4}[source]
"robust ecosystem" is a rather optimistic view of the rust situation... I would have said "a bunch of 0.x libraries that do 80% of the work and let you figure you the hard 20% while being slightly incompatible with each other, and that will be painful to glue together because of the rules for traits"
11. worldsayshi ◴[] No.41521682{3}[source]
Does Elixir inherit those batteries or is the ecosystem partially disconnected from Erlang?
replies(1): >>41521707 #
12. pjmlp ◴[] No.41521695[source]
Still doesn't have half of batteries that Python, Java and .NET include.
replies(4): >>41521882 #>>41521931 #>>41522719 #>>41531371 #
13. brightball ◴[] No.41521707{4}[source]
Elixir inherits it. You can call anything in Erlang directly.
replies(1): >>41527587 #
14. pjmlp ◴[] No.41521708{4}[source]
I rather have something that works everywhere there is a full implementation of the platform, instead of whatever third parties decided to support.
15. brightball ◴[] No.41521715{3}[source]
Supervisors give you the durability though.
replies(1): >>41522827 #
16. whalesalad ◴[] No.41521800[source]
I was looking for the same thing. A project like this really needs an `examples/` directory with a few projects to sink your teeth into.

I've been thinking for years that if a project existed like this for Python it would take over the world. Golang is close, I guess.

replies(2): >>41521998 #>>41522357 #
17. kbolino ◴[] No.41521832{4}[source]
The file API is mostly fine. Opening with that is the wrong approach to me; the comparison with Rust doesn't really illustrate any serious issues. All of the complaints could be addressed with better documentation.

The time API, on the other hand, is that bad, and worse. Besides the bizarre monotonic time implementation, there's also the bloated size of the time.Time struct, and the lack of actual "dates" (YYYY-MM-DD) and "times" (HH:MM:SS) that you can do arithmetic on. You can't really roll your own robustly either because time.Time isn't a great foundation to build on and //go:linkname is getting locked down.

Thankfully, build constraints are much better now, since they just use regular boolean operators, e.g.:

    //go:build windows && (i386 || amd64)
I agree that there shouldn't be any _buildtag.go magic, and I think they ought to remove that "feature" entirely in a future version of Go (which can be done so that it doesn't affect older or unversioned code). It seems they added a "unix" build tag too.

Also, one of my personal gripes is that the standard library exposes no guaranteed way to access the system CSPRNG. Any code can replace (crypto/rand).Reader with another implementation and compromise cryptographic operations. It's a trivial supply-chain attack vector, and even in non-malicious scenarios, it can be done mistakenly and break things all over the place in a subtle but dangerous way. The language developers have so far refused to fix it too.

Then there's log/slog with no TRACE or FATAL levels built-in; yeah, you can roll your own levels, but why should you have to?

replies(1): >>41524503 #
18. ◴[] No.41521859[source]
19. philosopher1234 ◴[] No.41521882{3}[source]
No doubt that is true, but are there particular batteries you’re thinking of?
replies(1): >>41522679 #
20. jddj ◴[] No.41521931{3}[source]
.net will even include every version of that battery since the battery factory first prototyped it in 2002
21. nvarsj ◴[] No.41521998{3}[source]
It's right there. https://github.com/ergo-services/examples

It looks like a close copy of Erlang APIs, albeit with the usual golang language limitations and corresponding boilerplate and some additional stuff.

Most interesting to me is it has integration with actual Erlang processes. That could fill a nice gap as Erlang lacks in some areas like media processing - so you could use this to handle those kind of CPU bound / native tasks.

  func (a *actorA) HandleMessage(from gen.PID, message any) error {
    switch message.(type) {
      case doCallLocal:
        local := gen.Atom("b")
        a.Log().Info("making request to local process %s", local)
        if result, err := a.Call(local, MyRequest{MyString: "abc"}); err == nil {
          a.Log().Info("received result from local process %s: %#v", local, result)
        } else {
          a.Log().Error("call local process failed: %s", err)
        }
        a.SendAfter(a.PID(), doCallRemote{}, time.Second)
        return nil
replies(4): >>41522124 #>>41522166 #>>41522289 #>>41524383 #
22. mjlee ◴[] No.41522012[source]
The examples are in another repo:

https://github.com/ergo-services/examples

23. Fire-Dragon-DoL ◴[] No.41522089[source]
How does the supervision tree look like? My major problem with go are goroutines bringing down tge whole software
replies(1): >>41527028 #
24. whalesalad ◴[] No.41522124{4}[source]
I can't tell which syntax is worse: erlang or golang.
25. yellowapple ◴[] No.41522145[source]
Are the actors/processes preemptively scheduled? I've seen many projects like this over the years, and that's the usual missing feature.
replies(1): >>41522158 #
26. Fire-Dragon-DoL ◴[] No.41522158[source]
Goroutines are preemptive scheduled, so the answer is yes
27. Fire-Dragon-DoL ◴[] No.41522166{4}[source]
This is crazy, so you could use it side-by-side to an elixir process
28. Thaxll ◴[] No.41522235{4}[source]
Rust has multiple http server and async framework which creates a lot of confusion.
29. fidotron ◴[] No.41522289{4}[source]
Honestly for Erlang integration just use NIFs or an actual network connection.

That golang is a mess, and demonstrates just what a huge conceptual gap there really is between the two. Erlang relies on many tricks to end up greater than the sum of its parts, like how receiving messages is actually pattern matching over a mailbox, and using a tail recursive pattern to return the next handling state. You could conceivably do that in golang syntax but it will be horrible and absolutely not play nicely with the golang runtime.

replies(2): >>41522803 #>>41522971 #
30. __MatrixMan__ ◴[] No.41522335[source]
I like that this is a thing, I'm trying to decide how excited I should be about it...

How equivalent are BEAM processes and goroutines?

replies(2): >>41522815 #>>41523150 #
31. ◴[] No.41522357{3}[source]
32. debo_ ◴[] No.41522418[source]
It has a great name, ergo I support it. /joke
33. pjmlp ◴[] No.41522679{4}[source]
GUI, dependency injection, huge collection libraries, dynamic runtime Interoperability, distributed objects, compiler plugins, custom runtime schedulers,... for example.

Some of the above will never be in Go due to how the community and language designers are philosophically against them.

replies(1): >>41523824 #
34. nerdponx ◴[] No.41522719{3}[source]
Don't forget Gauche: https://practical-scheme.net/gauche/man/gauche-refe/Finding-...
35. samuell ◴[] No.41522790[source]
This is super exciting!

I've written before about how I think the more FBP-style concurrency of Go and the message passing one in Erlang complement each other as much as streaming DNA processing inside the cell and fire and forget cell to cell signaling between cells do in biology:

https://livesys.se/posts/flowbased-vs-erlang-message-passing...

The FBP/CSP-style in Go being more suited for high performance streaming operations inside apps and compute nodes (or cells), while the message passing mechanism shines over more unreliable channels such as over the network (or between cells).

Ergo seems like it might allow both of these mechanisms to be implemented in the same language, which should be a big deal.

36. jerf ◴[] No.41522803{5}[source]
The ideal situation for this sort of code is to basically treat it as marshalling code, which is often ugly by its nature, and have the "payload" processing be significantly larger than this, so it gets lost as just a bit of "cost of doing business" but is not the bulk of the code base.

Writing safe NIFs has a certain intrinsic amount of complication. Farming off some intensive work to what is actually a Go node (or any other kind of node, this isn't specific to Go) is somewhat safer, and while there is the caveat of getting the data into your non-BEAM process up front, once the data is there you're quite free.

Then again, I think the better answer is just to make some sort of normal server call rather than trying to wrap the service code into the BEAM cluster. There's not actually a lot of compelling reasons to be "in" the cluster like that. If anything it's the wrong direction, you want to reduce your dependency on the BEAM cluster as your message bus.

(Non-BEAM nodes have no need to copy using tail recursion to process the next state. That's a detail of BEAM, not an absolute requirement. Pattern matching out of the mailbox is a requirement... a degenerate network service that is pure request/response might be able to coincidentally ignore it but it would be necessary in general.)

replies(1): >>41529126 #
37. kbolino ◴[] No.41522815[source]
Most significantly: goroutines are not addressable. This means that, from application code, you cannot send messages directly to a goroutine [1], you cannot link two goroutines, you cannot terminate a goroutine from another goroutine, you cannot directly watch a goroutine's exit [2], and there's nothing akin to the process dictionary (there are no "goroutine-local variables").

[1]: you usually use channels instead

[2]: you usually use sync.WaitGroup (or similar, e.g. errgroup.Group) instead

replies(2): >>41522875 #>>41540231 #
38. jerf ◴[] No.41522827{4}[source]
A different kind of durability than what people mean by "durable workflows", though. Durable workflows require durable storage of their current state; supervisors give you durable computation services. Supervisors don't even guarantee "durable computation"; if a process crashes halfway through processing a request, it's not like the request will be automatically retried or something. (That isn't even a good idea, there's a very good chance the reason why that request crashed one process will crash others.)
replies(1): >>41526438 #
39. Zambyte ◴[] No.41522875{3}[source]
Notably channels differ from actor mailboxes in that they have a capacity, and can block the sender. Mailboxes cannot block the sender, and they do not have a capacity in theory.
replies(2): >>41523022 #>>41523379 #
40. nahuel0x ◴[] No.41522944[source]
Three big differences in comparison with Erlang: 1- Cannot externally kill a process (yes, ergo process have a Kill method but the process will be in a "zombie" state until the current message handlers returns... maybe stuck forever) 2- No hot code reloading. 3- No per-process GC.
replies(4): >>41523113 #>>41523543 #>>41524544 #>>41525115 #
41. amdsn ◴[] No.41522971{5}[source]
NIFs have the downside of potentially bringing down the VM don't they? It's definitely true that the glue code can be a pain and may involve warping the foreign code into having a piece that plays along nicely with what erlang expects. I messed around with making erlang code and python code communicate using erl_interface and the code to handle messages pretty much devolved into "have a running middleman process that invokes erl_interface utilities in python via a cffi wrapper, then finally call your actual python code." Some library may exist or could be written to help with that, but it's a lot when you just wanna invoke some function elsewhere. I also have not tried using port drivers, the experience may be a bit different there.
replies(3): >>41524674 #>>41526978 #>>41529112 #
42. kbolino ◴[] No.41523022{4}[source]
The ordinary send operation blocks if the channel is full (or has no buffer), but you can send (or receive) without ever blocking, if you can accept that the message is dropped instead. It's not the most ergonomic operation, though:

    var sent bool
    select {
        case channel<-message:
            sent = true
        default:
            sent = false
    }
43. weego ◴[] No.41523043[source]
This was a constant source of confusion for me with Akka. They seemed almost proud of how much boilerplate and how many weird implicit conversions were exposed to the developer.
replies(2): >>41523090 #>>41526889 #
44. theflyinghorse ◴[] No.41523090{3}[source]
That's just Scala community in general.
45. didip ◴[] No.41523113[source]
(Not affiliated with Ergo) I have been watching Ergo repo for a while now and you wrote my observation concisely.

A while back, I tried to solve no.2 on your list by having a dynamically expanding and shrinking go-routines. POC here: https://github.com/didip/laborunion. It is meant to be used in-conjunction with config library & application object that's update-able over the wire. This idea is good enough to hot-reload a small IoT/metrics agent, but I never got around to truly solve the problem completely.

replies(1): >>41524133 #
46. metadat ◴[] No.41523134{4}[source]
Great example, and more of a horror show than I anticipated.

It would have been interesting if the author had a suggestion on what the Golang team should've / could've done instead, beyond correctly communicating the nature of the breaking change in the Go 1.9 release notes.

47. jerf ◴[] No.41523150[source]
My rather extensive earlier answer: https://news.ycombinator.com/item?id=34564228
replies(1): >>41524375 #
48. mervz ◴[] No.41523323[source]
Just use Elixir... it's a better language overall.
replies(1): >>41525503 #
49. brynb ◴[] No.41523379{4}[source]
with that said it’s quite easy to write an equivalent- https://github.com/redwood/redwood/blob/develop/utils/mailbo...
50. throwaway894345 ◴[] No.41523543[source]
I've never written any Erlang before--why do I care about per-process GC?
replies(5): >>41523595 #>>41523906 #>>41523962 #>>41524224 #>>41527473 #
51. com ◴[] No.41523595{3}[source]
Much lighter impact on system performance that world-GC. Simpler algorithms as well, so lower risk that you’ll ever get performance regressions - or worse.
52. limit499karma ◴[] No.41523824{5}[source]
> dependency injection

It's been a while since I played with the furry thing but is that even possible in Golang?

replies(1): >>41524462 #
53. everforward ◴[] No.41523867{5}[source]
I actually wouldn’t be surprised, if only because the standard library is so much harder to make backwards-incompatible changes to. I would generally expect that the average quality of the third party libs is lower, but the top 1% is probably better than stdlib.

Eg I don’t find the stdlib logging library particularly great; not bad, but not impressive. Ditto for the stdlib errors package before they added error wrapping

54. victorbjorklund ◴[] No.41523906{3}[source]
More consistent performance. No stopping the whole world.
replies(1): >>41524621 #
55. davisp ◴[] No.41523962{3}[source]
Also, for anyone not completely familiar with Erlang's terminology, the translation of "per process garbage collection" to Go would be "per goroutine garbage collection". As mentioned in a sibling comment, this allows Erlang style garbage collection to avoid pausing the entire operating system process when running garbage collectin.
replies(1): >>41524107 #
56. whizzter ◴[] No.41524107{4}[source]
Per-process GC is an optimization similar to nurseries in regular collectors, esp any object that has been sent in a message must be visible globally (yes there could be small object optimizations but that would increase sender complexity).

Also an overlooked part here is that the global Erlang GC is easier to parallellize and/or keep incremental since it won't have object cycles sans PID's (that probably have special handling anyhow).

TlDr; GC's become way harder as soon as you have cyclic objects, Erlang avoids it and thus parts of it being good is more about Erlang being "simple".

replies(3): >>41524336 #>>41524534 #>>41524592 #
57. mportela ◴[] No.41524133{3}[source]
Great project name, though
replies(1): >>41539654 #
58. petejodo ◴[] No.41524224{3}[source]
One reason also is I believe it has something to do with fault tolerance even at a hardware level. A process has its data isolated somewhere in memory, if something happens to that memory, the process will crash next time it runs and starts causing supervisors to start attempting to recover the system
replies(1): >>41524297 #
59. hinkley ◴[] No.41524297{4}[source]
Forced decoupling between tasks is part of the deal here. Each task can fail separately because it only affects other tasks through messages.
60. hinkley ◴[] No.41524336{5}[source]
Erlang is much more likely for GC overhead to grow sub-linearly, because more logic means more isolates (processes) rather than more state, more deeply nested, in the existing processes. Say at the square root of total data.
61. cynicalsecurity ◴[] No.41524349[source]
Go is not the correct language for this. This must be written in a language that support manually freeing variables from the memory. Go is a garbage-collected programming language.

Go is also not supposed to be used in a complicated way. Its use cases must remain simple.

replies(1): >>41525475 #
62. __MatrixMan__ ◴[] No.41524375{3}[source]
And a fantastic one at that. Thanks.
63. hinkley ◴[] No.41524383{4}[source]
I don’t know Go well, but this API would surely piss Alan Kay off.

Why a function that takes an Actor instead of each Actor being a type that implements a receive function? There’s so much Feature Envy (Refactoring, Fowler) in this example. There is no world where having one function handle three kinds of actors makes any sense. This is designed for footguns.

I also doubt very much that the Log() call is helping anything. Surely lathe API is thin enough to inline a that child.

replies(1): >>41526393 #
64. pjmlp ◴[] No.41524462{6}[source]
I was speaking about Python, Java and .NET standard libraries.

In Go you can naturally do it, by using the manual constructor approach, however there is no magic auto wiring like you can do with attributes and compiler plugins, plus standard libraries infrastructure for locating services, from those three ecosystems above.

replies(1): >>41525199 #
65. pjmlp ◴[] No.41524503{5}[source]
Anyone with enough experience in C derived languages large scale preprocessor spaghetti, welcomes _buildtag.extension alternative.

This is actually one of the few things I fully agree with Go designers.

replies(1): >>41525527 #
66. toast0 ◴[] No.41524534{5}[source]
Erlang avoids object cycles because it's impossible to make an old term point to a new one; data is immutable, so new terms can only referenece previous terms. This means the GC doesn't have to consider cycles and keeps things simple.

But that's separate from per process GC. Per process GC is possible because processes don't share memory[1], so each process can compact its own memory without coordination with other processes. GC becomes stop the process, not stop the world, and it's effectively preemptable, so one process doing a lot of GC will not block other processes from getting cpu time.

Also, per process GC enables a pattern where a well tuned short lived process is spawned to do some work, then die, and all its garbage can be thrown away without a complex collection. With shared GC, it can be harder to avoid the impact of short lived tasks on the overall system.

[1] yes yes, shared refcounted binaries, which are allocated separately from process memory.

67. neonsunset ◴[] No.41524544[source]
No per-process GC (still very configurable) but for hot-reload, if you don't mind a completely different language, there are Akka.net and Orleans:

https://github.com/akkadotnet/akka.net

https://github.com/dotnet/orleans

replies(1): >>41525860 #
68. neonsunset ◴[] No.41524592{5}[source]
> GC's become way harder as soon as you have cyclic objects

This may be true only for some implementations. Good GC implementations operate on the concept of object graph roots. Whether the graph has cyclic references or not is irrelevant as the GC scans the relevant memory linearly. As long as the graph is unrooted, such GC implementations are able to still easily collect it (or, to be more precise, ignore it - the generational moving GCs the cost is the live objects that need to be relocated to an older/tenured generation).

replies(1): >>41530012 #
69. throwaway894345 ◴[] No.41524621{4}[source]
That makes sense. I wonder how important this is versus Go, considering Go has a sub-millisecond GC even without per-process GC? (Go also makes much more use of the stack which might be sort of analogous to per-process GC?)
replies(2): >>41525191 #>>41529072 #
70. toast0 ◴[] No.41524674{6}[source]
Yeah, NIFs are dynamically linked into the running VM, and generally speaking, if you load a binary library, you can do whatever, including crashing the VM.

BEAM has 4 ways to closely integrate with native code: NIFs, linked in ports, OS process ports (fork/ecommunicate over a pipe), and foreign nodes (C Nodes). You can also integrate through normal networking or pipes too. Everything has plusses and minusses.

71. giancarlostoro ◴[] No.41524690[source]
This is strange, I don't know when it changed, but this project used to be compatible with Erlang directly...[0] Used to support the OTP. I guess their needs changed? You literally could write Go code that would talk to Erlang itself, I'm not sure why this has changed or where that functionality has gone but the page aside from a repository tag no longer mentions OTP. I was looking at this project for that very goal a month or two back.

[0]: https://news.ycombinator.com/item?id=34559409

replies(1): >>41526254 #
72. throwawaymaths ◴[] No.41525115[source]
Most beam developers will tell you they don't use hot code reloading, but if you're an elixir/phoenix (especially live view) you are using hot code reloading affordances in dev, though it's not the full suite of capabilities
replies(2): >>41526935 #>>41535763 #
73. weatherlight ◴[] No.41525191{5}[source]
This can cause GC to become "bursty."

BeamVM languages complete side step this problem all together.

replies(2): >>41526951 #>>41532876 #
74. limit499karma ◴[] No.41525199{7}[source]
Well, in my head DI at this point requires the magic bits. Otherwise it is (as you say) just constructor args.
replies(1): >>41528758 #
75. mr90210 ◴[] No.41525475[source]
I have never written anything in Erlang, but if my system requires some of Erlang’s built-in features, I am picking Erlang or Elixir.
76. Dowwie ◴[] No.41525488[source]
can I remote into a Ergo runtime and inspect state?
77. mr90210 ◴[] No.41525503[source]
People didn’t get the gist of your comment.

Here is another way of looking at it:

One could write mobile apps and mobile games in Go, but should you really?

78. kbolino ◴[] No.41525527{6}[source]
Is the file foo_bar.go (no build tag line) compiled or not?

If your Go version doesn't know of such a build tag as "bar", then foo_bar.go is unconditionally compiled. If Go in a later version adds "bar" as a known build tag, then foo_bar.go becomes conditionally compiled. Better hope you know this is how things work (reality: lots of Go devs don't).

Build tag lines don't have this problem. They always specify compilation conditions, even if the build tag is not already known to the compiler. They also apply to the whole file, the same as _tag in the name; there's no preprocessor spaghetti possible.

replies(1): >>41528743 #
79. rad_gruchalski ◴[] No.41525617[source]
My go to actor framework for golang has always been https://github.com/asynkron/protoactor-go. It seems that both protoactor and ergo are heavily influenced by Erlang. Why would one select ergo over protoactor?
80. throwawaymaths ◴[] No.41525677[source]
Can you link processes? goroutines? in ergo?
81. sbuttgereit ◴[] No.41525860{3}[source]
Of course... if you don't mind a completely different language and runtime stack... there's always Erlang & Elixir!
replies(2): >>41525898 #>>41527967 #
82. neonsunset ◴[] No.41525898{4}[source]
This is true, but they come with a different set of tradeoffs w.r.t ecosystem, tooling and performance (which turns into per-node efficiency in this case). There is also a matter of comfort and team/personal preferences.
replies(1): >>41526958 #
83. AlphaWeaver ◴[] No.41526254[source]
It's still present, they've moved it to a separate repository that's now commercially licensed.
84. justasitsounds ◴[] No.41526393{5}[source]
> I don’t know Go well, but this API would surely piss Alan Kay off.

> Why a function that takes an Actor instead of each Actor being a type that implements a receive function?

That function is a method with receiver type `Actor` - IE `Actor` implements this HandleMessage function.

Granted it is exactly equivalent to ``` func HandleMessage(a *Actor, from gen.PID, message any) error { ... } ```

But I'm happy sticking with composition over inheritance

replies(1): >>41527004 #
85. brightball ◴[] No.41526438{5}[source]
You can actually set it up that way though. You set a process to keep the state and a process to do the work. If the process doing the work crashes, the state is preserved.

It’s a deliberate decision but very easy with the BEAM.

replies(1): >>41527506 #
86. halfmatthalfcat ◴[] No.41526889{3}[source]
Your weird is another's flexibility.
87. hosh ◴[] No.41526935{3}[source]
I have used a portion of that capabilities for live debugging on prod machines to test things out before putting it through source code and using the regular CI/CD for deployment. However, the CD deployment does not rely on hot reloading because it hasn't been necessary.

On Kubernetes, running with regular pods it was not desirable. Maybe if we were deploying with StatefulStates.

88. hosh ◴[] No.41526951{6}[source]
Out in the field, smoothing out variances can be just as important to overall performance and reliability.
89. hosh ◴[] No.41526958{5}[source]
What do you mean by per-node efficiency?
replies(1): >>41528198 #
90. hosh ◴[] No.41526978{6}[source]
NIFs do have that downside. Rust NIFs mitigates some of those risks, but that doesn't work so well with other languages.

Port drivers have their own tradeoffs, but you can retain the fault isolation.

91. hinkley ◴[] No.41527004{6}[source]
Alright, so I did guess the structure correctly.

We aren’t building a ui framework it’s an actor. That’s a very small interface already.

92. helsinki ◴[] No.41527028[source]
How?
replies(1): >>41527242 #
93. helsinki ◴[] No.41527083[source]
What kind of practical, real-word problems can I solve with this, as a backend engineer at a hedge fund? Im struggling to understand why I even need actor frameworks when my processes are already supervised, pretty much never crash, and all communicate over gRPC and Kafka? Not trying to be snarky, just don’t understand how this paradigm could assist me.

For more context, most of my work is around real-time streaming / monitoring of trades and trading metrics.

replies(1): >>41528896 #
94. giovannibonetti ◴[] No.41527109[source]
Those looking for a type-safe actor model implementation may find the Gleam language [1] that runs on the actual BEAM VM and its OTP library [2] more interesting. It's type system is much closer to Rust, with native algebraic data types.

[1] https://gleam.run/ [2] https://hexdocs.pm/gleam_otp/

replies(1): >>41528972 #
95. stackghost ◴[] No.41527189[source]
Not to be confused with ergo, the ircd[0] nor with ergo, the cryptocurrency[1]

[0] https://github.com/ergochat/ergo

[1] https://github.com/ergoplatform

96. Fire-Dragon-DoL ◴[] No.41527242{3}[source]
The syntax for declaring it, sorry
replies(2): >>41527403 #>>41527478 #
97. mh- ◴[] No.41527403{4}[source]
I'm not the person you're replying to, but this left me with more questions than answers, haha. Can you expand on what you mean?
98. jerf ◴[] No.41527473{3}[source]
You don't. Go is generally significantly faster than Erlang and unless you are deeply concerned about the pauses themselves, you will more than recover GC time in generalized performance in almost all, if not all, cases.

Go's GC already has a lot of work done to minimize "stop the world" time down to very small values.

replies(1): >>41529083 #
99. Fire-Dragon-DoL ◴[] No.41527478{4}[source]
What's the equivalent of this: https://hexdocs.pm/elixir/supervisor-and-application.html

    defmodule KV.Supervisor do
      use Supervisor

      def start_link(opts) do
        Supervisor.start_link(__MODULE__, :ok, opts)
      end

      @impl true
      def init(:ok) do
        children = [
          KV.Registry
        ]

        Supervisor.init(children, strategy: :one_for_one)
      end
    end
In ergo
100. jerf ◴[] No.41527506{6}[source]
That's still not durable; you've set up a single point of failure in whatever is hosting the state process. Durable storage is a fundamentally different problem which you can't solve just by waving some Erlang processes at the problem. You could write a durable storage system in Erlang, although there's not a hugely compelling reason to do so when you can also use any existing one, and writing this sort of system is extremely challenging. Heck, just (fully) understanding Raft or Paxos is extremely challenging, let alone implementing it. Some classes of problems are best solved by a limited number of best-of-breed solutions that all of the other language ecosystems just use, like, durable storage and databases in general, or ffmpeg, or browsers.

(I can also tell you from personal experience Mnesia isn't a "durable storage" solution. Rather the opposite, honestly.)

replies(1): >>41533437 #
101. throwawaymaths ◴[] No.41527587{5}[source]
Not strictly true. Some stuff in the Erlang distribution must be included in extra_applications before you can call them.
replies(1): >>41527972 #
102. binary132 ◴[] No.41528032[source]
Every time I see people trying to build concurrency / process lifecycle frameworks on top of Go, I think they missed the point of Go. YAGNI. If you do really need it, just use erlang or whatever, where it’s world-class and battle hardened.
103. neonsunset ◴[] No.41528198{6}[source]
Amount of work a node can perform in a distributed system per X amount of hardware resources that it has.
104. pjmlp ◴[] No.41528743{7}[source]
I guarantee you it is even worse with preprocessor spaghetti, than build tools.
replies(1): >>41530792 #
105. pjmlp ◴[] No.41528758{8}[source]
Except that there are many ways to do DI, magic is not necessarly required, other than much welcomed development experience.

However Go doesn't like magic, thus that isn't something that will ever happen on the standard library, like on Python, Java, .NET.

replies(1): >>41529749 #
106. akvadrako ◴[] No.41528896[source]
You are probably thinking too course-grained. Erlang-style actors are closer to functions than processes.

For example you could parallelize a for loop by creating a goroutine for each item in a list. Or separate actors for logging, session tracking, connection pools, etc.

Doing that with processes and Kafka would be massive overhead.

107. widdershins ◴[] No.41528972[source]
I'm now sort of fascinated to know if it would be possible to compile Gleam to Go, and use this library to emulate the Erlang process stuff...
108. pdimitar ◴[] No.41529072{5}[source]
I have some production experience with Golang and one thing that helps it emulate Erlang's BEAM VM (also used by Elixir, FYI) is to have the goroutines be short-lived and/or disposable. No need for persistent workers most of the time anyway unless you are chasing every last millisecond of performance -- in those cases persistent workers waiting to snatch a job definitely perform better (though only by something like 1-2% in my limited experience; but that can be still a lot depending on hosting and workloads and customer expectations f.ex. in finance forgoing 1-2% perf is almost criminal).

So the BEAM VM definitely handles things a bit better but Golang can get quite close.

109. pdimitar ◴[] No.41529083{4}[source]
As much as I am a fan of Erlang's BEAM VM (and made a career out of Elixir that is still going today) I have to say that you are right -- Golang is doing extremely well and the incremental improvements over the last several years truly pulled it ahead. Unless you're constantly copying big objects and just produce a lot of trash then having your loaded program in Golang is going to be a super smooth sailing. And even if you stumble upon some of the traps, there is a lot of tooling to help you out identify a pain point and remedy it.
replies(1): >>41531478 #
110. pdimitar ◴[] No.41529112{6}[source]
Yeah, a NIF can bring down the entire OS process but I've used quite a bit of Rust NIFs with Elixir and never once had a crash. With Rust you can make sure nothing ever goes down, minus stuff that's completely out of your control of course (like a driver crash).
111. pdimitar ◴[] No.41529126{6}[source]
In my 8.5 years of Elixir practice I found it much easier to just use a Rust NIF or, in extreme cases, publish to an external job queue. Had success with one of Golang's popular ones (River); you schedule stuff for its workers to do their thing and they publish results to Kafka. Was slightly involved but IMO much easier than trying to coax Golang / Java / C++ / Rust nodes join a BEAM cluster. Though I am also negatively biased against horizontal scaling (distribution / clusters) so there's also that.
112. limit499karma ◴[] No.41529749{9}[source]
I guess we have to disagree here. The key word here is "injection". Dependency initialization, setting, passing, etc. none of these are 'injection'. But yes, Go lacks the necessary metadata mechanisms and per what you are saying it still does.
replies(1): >>41530896 #
113. whizzter ◴[] No.41530012{6}[source]
I'd like to see a reference to some GC actually doing explicit optimizations of this kind in a multithreaded scenario (not just as an indirect effect of theuir regular scanning), more or less linear scanning of memory is a natural consequence of a moving GC.

The Java gc's are doing some crazy stuff, my point however was that the acyclic nature of the Erlang object graph enables them to do fairly "simple" optimizations to the GC that in practice should remove most need for pauses without hardware or otherwise expensive read barriers.

It doesn't have to do a lot of things to be good, once you have cycles you need a lot more machinery to be able to do the same things.

replies(1): >>41534202 #
114. kbolino ◴[] No.41530792{8}[source]
I think we're talking past each other.

Build constraints with "//go:build" already exist, I'm not making them up or proposing something new: https://pkg.go.dev/go/build#hdr-Build_Constraints

This has nothing to do with preprocessor spaghetti, which is impossible in Go. Either a file is included or it is excluded. Neither with file naming nor "//go:build" can you cause only portions of a file to be compiled.

Really, the _tag.go mechanism is just a very simple kind of build constraint, equivalent to "//go:build tag". The problem is that it relies on a magic list of known build tags, and the contents of that list change over time. Apart from _test.go, which is a little too convenient to give up in my opinion, the rest of the tags could be pushed into build constraints, or at the very least, the list of known build tags could be frozen in its current state.

replies(1): >>41530886 #
115. pjmlp ◴[] No.41530886{9}[source]
Build contraints are the version of Go's preprocessor spaghetti, when those conditions get so complex, that one needs pen and paper to understand what is actually included.
replies(1): >>41531518 #
116. pjmlp ◴[] No.41530896{10}[source]
Injection doesn't mean automatic, or magic.

And if one goes back 20 years, constructor based injection was the norm, the magic only came later thanks to Aspect Oriented Programming, yet another tool that Go will never adopt.

replies(1): >>41531612 #
117. throwaway81523 ◴[] No.41530941[source]
I remember wanting to do a Go port of OTP and call it GoTP. It's hard for supervision trees to work properly in Go, unfortunately.
118. Xeoncross ◴[] No.41531371{3}[source]
As someone that writes Java and Python, I'm unsure what you're referring to here by "batteries" other than 3rd party packages.

Even something as basic as JSON encoding requires an external package in Java.

119. jerf ◴[] No.41531478{5}[source]
I am a big believer in the idea that engineers, real professionals, need to have a clear view of what kind of performance technologies deliver, and should never ever view performance statements as a political statement. They may be right or wrong, but they aren't political. This includes both not writing a project that needs very high performance and reaching for a known-lower-performance tool, and also not reaching for the absolutely highest performance tool which generally comes with a price when you are orders of magnitude away from needing it.

Erlang/Elixir has plenty of performance for plenty of problems, but Go is definitely generally a cut above. And there's definitely another cut above Go in performance, it's not the fastest, and there's a cut below Erlang/Elixir as well because they're generally faster than the dynamic scripting languages. And even the dynamic scripting languages are often fast enough for plenty of loads themselves.

120. kbolino ◴[] No.41531518{10}[source]
Well, then it ought to go the other way. If you create foo_bar.go and bar is not a known build tag, you should get a compiler error.
121. limit499karma ◴[] No.41531612{11}[source]
Sure, but I don't recall it being called "injection". Obviously we are in agreement that dependency references are introduced via some mechanism. Also AOP came [after] Java J2EE and Spring. However I will concede that there is a distinction between 'inversion of control' and general DI.
122. throwaway894345 ◴[] No.41532876{6}[source]
What specifically causes GC to become bursty? Presumably a low latency GC is not "bursty" more or less by definition?
123. brightball ◴[] No.41533437{7}[source]
I need to find the link, but I saw an incredible presentation at Gig City Elixir last year where they’d been running a globally distributed, in memory, medical database with no downtime or data loss for upwards of 5 years. One of the coolest projects I ever saw.

But yes, you do have to determine state and recovery patterns. Depends entirely on the situation but things like making sure your data will survive a process crash is straightforward.

124. neonsunset ◴[] No.41534202{7}[source]
Personally, I'm disappointed that there is too much superstitions and assumptions about GC designs going around, which lead to the discussion like this one that treats specialized designs with explicit tradeoffs, which both Go and BEAM are, as universally superior options. Or discussions that don't recognize that GC is in many scenarios an optimization over malloc/free and a significant complexity reduction over explicit management of arenas.

When it comes to Java - it has multiple GC implementations with different tradeoffs and degree of sophistication. I'm not very well versed in their details besides the fact that pretty much all of them are quite liberal with the use of host memory. So the way I approach it is by assuming that at least some of them resemble the GC implementation in .NET, given extensive evidence that under allocation-heavy scenarios they have similar (throughput) performance characteristics.

As for .NET itself, in server scenarios, it uses SRV GC which has per-core heaps (the count is sizing is now dynamically scalable per workload profile, leading to much smaller RAM footprint) and multi-threaded collection, which lends itself to very high throughput and linear scaling with cores even on very large hosts thanks to minimal contention (think 128C 1TiB RAM, stometimes you need to massage it with flags for this, but it's nowhere near the amount of ceremony required by Java).

Both SRV and WKS GC implementations use background collection for Gen2, large and pinned object heaps. Collection of Gen0 and Gen1 is pausing by design as it lends itself for much better throughput and pause times are short enough anyway.

They are short enough that modern .NET versions end up having better p99 latency than Go on multi-core throughput saturated nodes. Given decent enough codebase, you only ever need to worry about GC pause impact once you go into the territory of systems with hard realtime requirements. One of the better practical examples of this that exists in open source is Osu! which must run its game loop 1000hz - only 1ms of budget! This does pose challenges and requires much more hands-on interaction with GC like dynamically switching GC behaviopr depending on scenario: https://github.com/dotnet/runtime/issues/96213#issuecomment-... This, however, would be true with any language with automatic memory management, if it's possible to implement such a system in it in the first place.

replies(1): >>41556604 #
125. lakomen ◴[] No.41534267[source]
A more hands on demonstration would be welcome. This generic demo with its naming isn't helping.

What is the connection between an actor and a database for instance?

Why do I need actors?

126. gregors ◴[] No.41535763{3}[source]
Hot code reloading is much more popular with Erlang developers. It's not popular at all in elixir which is a shame honestly.
replies(1): >>41536372 #
127. throwawaymaths ◴[] No.41536372{4}[source]
Whether or not it's popular, if it weren't a thing for Erlang I doubt Phoenix live reload would work.
128. justinclift ◴[] No.41539654{4}[source]
That could be another way to have AWS not poach a popular Open Source project. Name it something extremely pro-unionisation, such that every time they write the name they'd be reminding their developers that unions exist. :)
129. pancsta ◴[] No.41540231{3}[source]
> you cannot send messages directly to a goroutine

Goroutines communicate through channels, all you need is a queue (eg buffered chan).

> you cannot terminate a goroutine from another goroutine

Termination is propagated via context cancellation. go-A cancels ctx, go-B waits with `select`, reads from `<-ctx.Done()` and does a `return`, or checks it after each blocking call.

> there are no "goroutine-local variables"

Not sure if I got this one, but every var in a function's scope, which has been `go`-routined, would qualify.

I'm currently working on a lib/framework somehow related to Ergo, but taking a more "generic" approach of a state machine[0]. It may solve some of the mentioned issues with Go, like addressing and queues for communication.

You seem to be very attached to an idea of using the same goroutine for a long time, whereas it's usually more dynamic and only schedulers are long lived `go`-s.

[0]: https://github.com/pancsta/asyncmachine-go

replies(1): >>41549757 #
130. kbolino ◴[] No.41549757{4}[source]
Please note that the context of these points is in comparison to BEAM processes, as used by Erlang and Elixir. It is a comparative analysis of goroutines, not an holistic critique of them.

I appreciate the mention of context cancelation, that is a good example of how to terminate a goroutine, provided that a) the goroutine has a context to cancel and b) the goroutine does not spend too much time in operations that can't be canceled (e.g., file system calls, time.Sleep, blocking select without case <-ctx.Done(), etc.). This is still cooperative multitasking though, while BEAM processes are fully preemptive.

A "goroutine-local variable" would be a variable accessible by other goroutines which is still scoped to a particular goroutine. Ordinary local variables are only accessible by the goroutine in whose stack they belong. Something like the process dictionary can be constructed in Go using a synchronized map with atomic values, but it certainly isn't a built-in part of the runtime like it is in BEAM.

I generally don't regard goroutines with much attachment. There are, however, some situations in which goroutines must be long-lived: the main goroutine, the "scheduler" goroutines you mentioned, and in general any goroutine performing a supervisory role over other goroutines. Monitoring these important goroutines is more complicated in Go than BEAM languages, but certainly isn't impossible.

The more problematic aspect of goroutines vs. BEAM processes is that, due to the lack of application-accessible preemption, when a critical-path goroutine does get stuck, the only real solution most of the time is to kill the entire program. This is rarer than it was in Go's early days, at least.

131. whizzter ◴[] No.41556604{8}[source]
I'm not gonna say it with 100% certainty, but having started using C# a few years ago i think that much of the smaller latency is simply because C# probably produces a magnitude less garbage than Java in practice (probably less than Go as well). The C# GC's generally resemble the older Java GC's more than G1 or especially the Zgc collector(that includes software based read-barriers whilst most other collectors only use write-barriers).

Small things like tuples (combining multiple values in outputs) and out parameters relaxes the burden on the runtime since programmers don't need to create objects just to send several things back out from a function.

But the real kicker probably comes since the lowlevel components, be it the Http server with ValueTasks and the C# dictionary type getting memory savings just by having struct types and proper generics. I remember reading some article from years ago about re-writing the C# dictionary class that they reduced memory allocations by something like 90%.

replies(1): >>41572380 #
132. iknowstuff ◴[] No.41564060{5}[source]
Uh, which ones would you like to compare? C++ std regex vs rust’s regex crate?
133. neonsunset ◴[] No.41572380{9}[source]
When you say "C# GC's generally resemble the older Java GC's more than G1 or especially the Zgc", what do you have in mind? I'm skimming through G1 description once again and it looks quite similar to .NET's GC implementation. As for Zgc, introducing read barriers is a very strong no in .NET because it introduces additional performance overhead to all the paths that were previously free.

On allocation traffic, I doubt average code in C# allocates less than Go - the latter puts quite a lot of emphasis on plain structs, and because Go has very poor GC throughput, the only way to explain tolerable performance in the common case is that Go still allocates less. Of course this will change now that more teams adopt Go and start classic interface spam and write abstractions that box structs into interfaces to cope with inexpressive and repetition-heavy nature of Go.

Otherwise, both .NET and Java GC implementations are throughput-focused, even the ones that target few-core smaller applications, while Go GC focuses on low to moderate allocation traffic on smaller hosts with consistent performance, and regresses severely when its capacity to reclaim memory in time is exceeded. You can expect from ~4 up to ~16-32x and more (SRV GC scales linearly with cores) difference in maximum allocation throughput between Go and .NET: https://gist.github.com/neon-sunset/c6c35230e75c89a8f6592cac...