I'd be pretty nervous about any attempt to recreate Finagle in Go. Scala provides many ways of creating composable complexity. Finagle leverages pretty much every feature of Scala (and there are many) to maximize this composability in a sane way, but because there is simply so much complexity in Finagle, a lot of opportunities for composability have been skipped due to lack of need within Twitter (maybe finagle-http has significantly improved in the last year, which is mostly what I'm thinking about when I say this) but it's a LOT of complexity, sort of like the RoR of distributed systems, and when you want to diverge from Twitter's chosen trade-offs, it may be a hellish experience. It's pretty sweet for when your problem space aligns with Twitter's, however.
Go has basically none of the mechanisms for sanely creating composable complexity that Scala has. It makes me really grossed out to think about something with the complexity that Finagle exposes as a Go library. Go is all about small, sturdy tools that work for most of the cases most of the time and that compose well with each other - IF - they are built into the standard library. And if it's not, have fun spending your time writing uni-typed interfaces for any kind of interop with desired complexity. Go is great for writing dead simple byte slingers, pipelines that will be rigid in the most dynamic possibility, guns that shoot JS and interact with services, etc. That's simply not Finagle.
Could you provide examples (beyond generics) where 'composable complexity' helped finagle achieve a goal?
My experience with Scala has been that an author's 'composable complexity' becomes a reader's 'wtf is this complexity'.
Go's philosophy has been to avoid complexity altogether in most of the tools they give you, forcing you to write lowest-common-denominator code that anyone can easily understand. Generics are sorely missed, but I really don't see additional expressive power from most scala features, just additional things I need to keep in my head for an occasional 5-line savings in the middle of some method (which IMO doesn't change the complexity equation at all). Would love to see some beautiful examples that change my mind.
Clarity of thought is rare. The ability to express those thoughts as code is even more rare.
Go's approach is to makes it easy to say certain things. Goroutines are great. But it also makes it difficult to say complicated things like List<T>. Good programmers suffer, but really it's just a few more keypresses, ala Java.
Scala makes it possible to say even more abstract things like T<int>. Not many programmers can really explain a higher kinded type, or why such a thing is useful. Novice programmers say silly things because they don't really know how to apply all of this abstract power.
Go is pretty wonderful because it lets programmers of varying ability work together effectively. Scala is teaching people new abstractions. Are they worth it? Maybe not in Scala. However, I think languages with powerful type systems will ultimately win, because you can lean hard on the compiler to guarantee correctness.
You forgot the other drawbacks that complicated type systems have: hellish compiler messages and long compile times. Those don't go away just because you learn more about the language.
I also dispute the idea that only novices abuse complicated type systems. Actually I think it's the reverse... only people who know the language well can create something truly horrible. Don't believe me? Look at any of the C++ STL header files and experience Biij.
Of course, just because they can doesn't mean they will. But in a world dominated by people of average intelligence, working around the programming equivalent of a gas lamp with an open flame just isn't a good idea.
> drawbacks that complicated type systems have: hellish compiler messages and long compile times
Scala has incredibly nice compiler messages and you can see Rust for an example of a language with complex types and short compile times.
These issues have _nothing_ to do with complicated type systems - you're projecting the failures of the C++ template system - failures which stem in fair part from the fact that it's not really a type system but simply a templater.
Finally, it's worth recognising that Scala goes to lengths to keep messy features inside their own compilation unit - for example, macros appear as functions to the outside and complex features such as implicit conversions must be explicitly imported for use.
I tried building our project in Go and Node.js explicitly to make it as easy as possible for people to contribute, irrespective of their background. It turned out that the additional complexity I had to pump into abstractions to make it happen made the code just as complex to understand.
It's quite possible that projects just have a natural complexity level and it's worth picking the language to match. There are certainly different times for different languages.
As much as I like Rust, I have to say that it's not advancing a compelling argument for "fast compilers with complex types" (which isn't to say I don't think it can be done, just that Rust is by no means fast at compiling, and really only gets away with the slowness it has because its main competition still lacks a proper module system).
Rust's type system has nothing to do with any slowness in its compiler. There's a `time-passes` flag that you can pass to the compiler to see where it spends its time compiling any given program. Do so and you'll see that the overwhelming majority of Rust's compilation time is spent inside LLVM generating code.
Go libraries are typically highly composable because they operate mostly on primitives or strictly defined common interfaces. Orthogonality is a key feature.
I agree with you completely, yet what could prevent the gokit team from starting with the definition of very simple , orthogonal, interfaces and maybe add the very few new types required, to immitate the go stdlib.
I'm quite sure the result will be quite different from what those kind of frameworks look like in other languages, but that could be interesting still.
They are type safe, except for the unsafe package but it's hard not to know what you're getting into with that one.
One of the best examples of composability is the Reader and Writer interface. They're designed to operate on slices of bytes, so a package intended to read from a Reader can read from files, stdin, networks, sockets, and a myriad other systems with absolutely no changes to the package's code or its implementation.
Main.hs:19:1-100: Warning: …
Pattern match(es) are non-exhaustive
In an equation for ‘orderConfirmationMessage’:
Patterns not matched:
Media _ _ Free
Media _ _ Buy
Media _ _ Subscriber
Good thing GHC had told us Free/Buy/Subscriber's needed messages instead of our boss! In fact, had I been using -Wall this would be a compile time error.
Getting to the point, my real questions are:
1. What is Go's way of ensuring your function handles cases like this, or is there an idiomatic way of avoiding it?
2. How do you discern between MovieType's without resorting to just using Strings and compromising type safety?
There is no exhaustive pattern matching in golang. And you would have to define your own unmarshall function that translates "Rent" into, perhaps, a const. So, yes, type safety in golang is not as powerful as it is in Haskell. I think we already knew that though didn't we :)
> So, yes, type safety in golang is not as powerful as it is in Haskell. I think we already knew that though didn't we :)
That is just a really big one for me I wanted to share and get others opinion on. I guess I just feel like a lot of people don't realize how much having that stringly typed hole means. Perhaps others just disagree.
I would love to hear a counter argument for why others don't think this matters.
I've been coding professionally for 16 years. I've written a lot of this kind of code:
switch (someEnum) {
case foo:
<something>
case bar:
<something>
default:
throw DeveloperScrewedUpException("Unknown enum val: " + someEnum)
}
...You know how often I've seen those exceptions? Never. Not once. I'm not saying it can't happen, and I'm not saying that I wouldn't want a compiler error to tell me I missed a value... but I am saying that it just doesn't actually happen all that often in my experience, especially with decent tests. And yes, I've worked on systems for many years where they've evolved and things have changed significantly, so it's not just that I've only worked on brand new projects.
A client is a layer of various components like load balancers and stats recorders, but they're also all configurable. Only using the expressiveness of scala could such a data structure be created to compose all of these pieces, yet still keep them modifiable and type-safe.
I didn't mention in it my sibling post here, but we started writing the core of wym (our microservices framework) in Go, then Node.js and finally settled on Scala for exactly the reasons you outline.
Writing in those other languages was indeed a hellish experience - it was very clear that they lacked the expressivity to model half of even just this very core piece.
> Writing in those other languages was indeed a hellish experience - it was very clear that they lacked the expressivity to model half of even just this very core piece.
This is something that keeps sending me back to Haskell, I'm glad to hear Scala applies as well.
I've been meaning to learn more Scala, is there any "Scala for Haskellers" literature you are aware of? I'm going to start searching for some, but you could know of one that doesn't come up in my search results.
There is an awesome book called "Functional Programming in Scala" that has a lot of great questions to work along to, and is great for somebody interested in both Haskell and Scala but doesn't assume prior experience with things like combinators and monads. If you are pretty far down the Haskell route and want to learn about one of the more advanced type-level libraries, You may want to jump into http://eed3si9n.com/learning-scalaz/ and use https://twitter.github.io/scala_school/ as a quick reference or intro. If you have the time and desire, a lot of people swear by a cover-to-cover reading of Odersky's "Programming in Scala".
Generally speaking, Go is not a good fit for frameworks (which are meta-tools by definition). Implement the actual tool itself in Go, not a framework to build the tool.
I would disagree with this - it's a very broad statement. While building frameworks often encourages developers to abstract to the nth degree and add far more flexibility than is required, if you have specific requirements what's the problem?
We built a microservice architecture in Go at Hailo which runs approx 170 services. Abstracting core functionality into a 'framework' allowed developers to build and deploy services extremely quickly, and let the platform team get on with adding and improving core platform functionality, such as monitoring, instrumentation, tracing, provisioning, configuration etc etc.
One of the things I love about Go is that I don't have to learn all kinds of conceptually heavy frameworks to get real work done. I read the stdlib docs and get to coding.
I've also found that when I'm tempted to stray from Go idioms and do something too generically, I tend to get punished by the language. So I've learned to just suck it up and do everything the "Go way". I don't consider this a negative--rather that Go is just not very tolerant of other language styles. You have to accept this.
I think the best way to describe it would be to make a distinction between frameworks and libraries. A framework integrates itself too much into the application's overall structure, while a library provides another tool that lets you write your code the way you've always been writing it, but to do something new.
The weakness of the type system becomes a problem pretty quickly - simply having a different implementation of an adaptor, be it per language, transport or even simply type of node makes for an endlessly leaking abstraction.
That's what I felt that overlapped with the GP's comments on composing complexity. It's not so much that you _can't_ implement multiple cases (not to the nth degree, just a handful), it's that the abstraction leaks painfully.
I find your and the parent's arguments really interesting.
For me Go reminds me so much of Java 1. And it seems to be getting pulled by the community in the enterprise Java direction e.g. generics, annotations, dependency injection, broad frameworks and the simple, tightly focused tooling direction.
The community isn't pulling Go into any of those things.
* Go had annotations since pretty much day 1. Look at the annotations for JSON serialization, for example.
* Generics is something that Go is probably not getting. It's just become a troll topic at this point since everything that could be said about this topic, has been said.
* Dependency injection is something that Go isn't getting. It's a Java solution for a Java problem. Create well-thought out code with clean interfaces and unit tests... problem solved.
* Broad frameworks: it's just not going to happen. As a lot of other commenters have said, Go just doesn't support the level of abstraction that you need to create a FactoryPatternPatternStrategy. And that is a good thing, in my opinion.
* Tooling: I have no idea what you mean really. Java's tooling is hardly "simple" or "tightly focused" (have you ever USED Maven?) and Go's community didn't "pull" the golang authors to create simple tools... they've been simple and focused since day one.
I would say one of the benefits of Scala is that it allows greater abstraction than some other languages which can provide more clarity. Example a service in Finagle is defined as;
Request => Future[Response]
I don't know what the equivalent would be in Go but I can't imagine its any clearer than this.
Now make Request and Response generic so that you can tell different requests and responses apart.
Then make Future generic so that you can use all the existing combinators out there, like `sequence` which turns `List[Future[Response]]` into a Future[List[Response]]`.
Your reply actually highlights one of the key features of Go: if you design it right using channels, constructs like Futures and Promises aren't necessary.
> design it right using channels, constructs like Futures and Promises aren't necessary
Careful implying that using futures and promises are incorrect unless you can prove that channels can do anything futures and promises can do while doing it better.
In Go they are incorrect at worst, not idiomatic at best. That's due to the nature of the language as channels are built-in primitives capable of fulfilling the roles of either, not a slight on the efficacy of the constructs themselves.
I don't think the idea is to recreate Finagle in Go, but to create a toolkit that solves the same set of problems that Finagle solves in Go.
And I don't think the problem domain is intractably complex; I think we have enough collective operational experience to have informed opinions, and I think Go's type system may be sufficiently sophisticated to encode those opinions.
Go has basically none of the mechanisms for sanely
creating composable complexity that Scala has. It makes
me really grossed out to think about something with the
complexity that Finagle exposes as a Go library. Go is
all about small, sturdy tools that work for most of the
cases most of the time and that compose well with each
other - IF - they are built into the standard library.
And if it's not, have fun spending your time writing
uni-typed interfaces for any kind of interop with
desired complexity. Go is great for writing dead simple
byte slingers, pipelines that will be rigid in the most
dynamic possibility, guns that shoot JS and interact
with services, etc. That's simply not Finagle.
Go has basically none of the mechanisms for sanely creating composable complexity that Scala has. It makes me really grossed out to think about something with the complexity that Finagle exposes as a Go library. Go is all about small, sturdy tools that work for most of the cases most of the time and that compose well with each other - IF - they are built into the standard library. And if it's not, have fun spending your time writing uni-typed interfaces for any kind of interop with desired complexity. Go is great for writing dead simple byte slingers, pipelines that will be rigid in the most dynamic possibility, guns that shoot JS and interact with services, etc. That's simply not Finagle.