I'm a fan of the Rx programming model, but is it really worth it to introduce such a library and model into a language with such an opinionated and built-in model for streaming/pipelining and concurrency?
I was wondering the same thing. "Reactive" is really just (misnamed[1]) dataflow[2], as far as I can tell, and Go has dataflow in spades. Of course, it would be nicer if Go's dataflow mechanism were adaptable/extensible in a Open Implementation style[3].
The streaming/pipelining is the least of the issues here. The impossibility to define a generic compile time safe API makes these kind of projects useless in practice. It's just impossible to write abstractions in Go in a compile type safe way. These untyped API just feel dirty for a modern statically typed language.
Yep. 100% agree. I think you have to treat Go as a dynamically typed system with some static typing thrown in, if you want to use it beyond its core use case.
especiallt when the type system is so weak that you'll have to use interface{} everywhere.
This library sums up a lot of the language's problem : channels aren't the final word on concurrency, and go needs at least some way of making reusable code in a type safe manner.
This isn't true. You could use the adapter pattern and require that your reactive elements implement some kind of sane interface and then you're not using interface{} everywhere. It's not as flexible as generics but it's possible. This is how you do promises in go, with the adapter pattern.
But then that interface needs to specify the behavior. And -- crucially -- ONLY that behavior. It's not possible in Go to say "this thing is unknown, but it will have a specific behavior (type) at compile time." interface{} says it is known type that has no behavior.
Is that not the definition of what a interface is? If you want something that prints you define a printer interface that has exactly one method. Go highly encourages small interfaces
The problem is we need the distinction between "author of the code" unknown ( in which case whatever symbol such as T is enough), and "user of the code" unknown ( in which case relying on dynamic casting or interfaces is fine).
What go doesn't provide is 1/ unknown but 2/ not. Which happens when you're creating a lib to be used later.
If you don't want to use this nobody will force you. The overwhelming and unfortunately predictable heap of negativity that comes bearing down on people sharing something cool they made is really awful.
Well someone has made this, and now you can decide if it's worth it for you as an individual to decide whether you like it or not and then if you don't, then don't use it.
I find it incredibly ironic and hypocritical that you accuse me of creating a negative environment... in a negative reply to a fairly neutral question.
I don't use Go, but am a contributor to a Rx project for a different language. As such I am not interested in using this but genuinely curious about the potential (non)benefits of Rx in Go, especially given that they both promise to solve similar problems in very different ways. From experience HN is the prime place for such discussions.
I think HN would be a much worse place if we would only be allowed to cheerlead projects here.
Do you mean to imply the parent is being negative? I think its a perfectly valid question. Unlike languages like Java which only have concurrent primatives Go is very opinionated how about to do concurrency.
You're right, let's not discuss the merits of any technical solutions. The purpose of a comments section is to shower people with praise. This is because negativity is bad and positivity is good.
And whatever we do, let's avoid discussing software design choices at all costs. Surely the gains in positivity will make us better programmers.
On the one hand this seems like a strange decision given that Go purposefully avoids asynchronous code. On the other hand, I think this project is trying to get at something that is (IMHO) a blind-spot for the Go community. Namely: I find it difficult to design abstract data-flow patterns and encapsulate behind a nice API.
Go is great for dealing with the nitty-gritty of concurrency, but I find it hard to express a high-level vision of what's going on in my app/lib. This is why this project almost feels good. There's a clear view of what we're manipulating and how we're positioned relative to the data.
Has anybody else struggled with this?
The closest thing I've found to a solution has been go-mangos[0], a native go implementation of the scalable protocols. This is extremely useful for designing the data-flow of an application when I'm shuffling bytes around, but what I find myself wanting is an equivalent system for raw (i.e.: unserialized) Go data-structures.
I want to be able to do something like this (assume a hypothetical library to this effect called `portal`):
p0, _ := portal.NewPortal(iface, portal.REP) // iface is a types.Interface
p0.Bind("foo")
p1, _ := portal.NewPortal(iface, portal.REP)
p1.Dial("foo")
go func() {
for {
i, _ := p0.Recv() // `i` guaranteed to satisfy the interface represented by `iface`
i.DoSomething()
p0.Send(i)
}
}()
p1.Send(iRequest)
iResult, _ := p1.Recv()
I'm very curious to get some feedback on this idea. It's been gnawing away at my brain for a while now.
Not really. Channels have a single function, to transport data to the first available consumer.
With observables, there's a whole world on top of that. The simplest observable, when subscribed to, will cause the producer of content to start the production. If 3 consumers subscribe to an observable, then the producer will do it's work three times, once per subscription. Compare that to a channel, where a producer has no way of knowing if a consuer is available. Generally, they start the work immediately and try to stuff the result in a channel, regardless of whether there's a consumer on the other end. Moreover, when another consumer uses the same channel, you have two consumers fighting for the same result.
And since observables are transformable, there's a plethora of operators available (at least in rxjava). If you take the previous example, you might want to share the observable, replaying the last result, so that regardless of how many consumers subscribe to it, the producer will only do the work once, and the consumers will receive the same results. It will be quite a bit harder, though not impossible, to implement the same thing with channels.
FWIW not exactly related, but check out Go-LINQ as well. https://github.com/ahmetalpbalkan/go-linq (disclaimer: my project). I will take a look at combining RxGo and Go-LINQ and probably will write a blog post soon.
Promises are to Streams what single values are to arrays/collections.
Promises are all fine and dandy for "do this, wait for that, do that", but they are not powerful enough to express "every time this happens, do that".
I guess you're coming from a JS background as you are using the word "Promises". Imagine instead of registering a callback for mouse click events, you have a stream of mouse events where you can listen to. This is exactly how it works in Angular 2 [1].
This http://rxmarbles.com will shows a little better the possibilities of combining rx streams.
Now carefull, because it adds a lot of complexity, sometimes for very little gain, and sometimes for even harder to debug code. It also has a high learning curve, because it encourages you to make your code declarative rather than imperative, which isn't always obvious to do.
Promises let you compose a program to eventually process a single result. They're oriented around a single event. Observables let you compose a program to process a stream of values.
In practice, there's an abstraction jump with observables. Promise compositions are usually built in response to an imperative action. Observables are built to replace imperative event handlers altogether.
If you leave out the unpleasant first sentence, I think the post is fine:
Once a language becomes popular enough, huge hordes of fad-oriented programmers move in and overcomplicate everything to hell and back. See JEE, IoC frameworks, AOP, runtime bytecode modification, patterns out the wazoo, OOP hierarchies, etc. I think Go was deliberately designed to sabotage some of these attempts, and thus far it's been successful at staying uncomplicated. I hope it has managed to build enough of the immune system for fads to not get much traction. Stay boring and simple, Go. I like my tools boring and simple.
Most languages are designed with technical power/semantic guarantees while writing code as the primary goal. However, programmer-oriented HID accounting proportionally for all parts of the edit-test-debug cycle and production/maintenance should really be the first priority. I think that all successful languages that get pooh-poohed for being boring, being Blub, or being technically inferior must be doing something right when it comes to the above.
Think about it. Do we know of any other products where people have a misguided narrow focus on "specs," but which can be improved tremendously by thinking about the overall experience? Laptops, cars, and apps come to mind. Why shouldn't such a principle also apply to development stacks?
As a person who's looking to hire people, I'm signaling them the opposite. We use C++ with only a few maximally boring dependencies. And even in C++ we stick to C plus smart pointers, and a very occasional class where it makes sense.