Hacker News new | past | comments | ask | show | jobs | submit login

I was deep into effect languages (Koka) and did some effects on my own in Scala for some years.

After a year with trivial Go I just wonder why.




I didn't want to put down the effort of the OP, because even they admit it's an interesting experiment, not something they recommend that anyone use, and I do think it's an interesting experiment. But I have exactly this feeling in general.

I like the idea of Rust, but I feel it has, unfortunately, been taken over by the seductive idea that abstraction is the purpose of a programming language (e.g the C++ crowd, among others). I can say from long experience, that while it probably won't impede its popularity, this isn't real progress in programming language design. These abstractions just create their own problems to solve on top of solving the original problem you wanted to solve by writing a program in the first place.

Inevitably, that means that you end up having to limit yourself to some particular "idiom" or subset of the language in production, so that you can get anything constructive done without the code being inscrutable or unmaintainable or just overblown for the task that's being performed.

I knew it was over when they started debating adding more and more meta-language type system features, and then added async/await -- which is the very definition of creating a problem to solve a problem.

So, as much as I appreciate Rust, I am looking forward a newer systems language with more discipline in its design and direction.

Go isn't perfect, but it definitely trends to the right flavor of simplicity and design discipline.


I like Rust a lot, enjoyed working with it for a year, I'd just wish the borrow checker would be easier with structs that have data from different sources - so I also didn't want to put down the effort and quite frankly might take a look.

"been taken over by the seductive idea that abstraction is the purpose of a programming language"

This downed Scala.


Could you elaborate? What did you find unnecessary or bad about effects?


I've dug deep into Haskell, and my day-to-day job is Go, so I can probably sort of answer this at least for myself.

A lot of time, heavy-duty functional programming advocates will use as their foil what you might call either the worst of imperative programming messes, or if you want to be more generous, an "average" imperative programming mess, where I include OO in the "imperative" category for the purposes of this conversation. Against this they will present their world of pure programming and recursion-scheme-based programming and super strong, complicated type systems, and claim it is better.

I agree, for the most part. I mean, I could elaborate and add half-a-book's worth of nuance to that, but for now, I agree.

However, when I, personally, sit down in front of Go and program with it, I am not some abstract "imperative programmer". I am someone who is writing Go having been informed by the lessons from Haskell and Erlang. My code is not perfectly functional, because that is not the optimum, but you don't see global variables flying everywhere in my code. You don't see my code being written where everything takes a dozen locks simultaneously to do anything. You see actors constraining things. IO may not be rigidly separated by a type system, but in a lot of my code you can see that I isolate IO behind an interface.

If you are feeling a bit generous and squint a bit, I write a lot of my Go code as a free monad with an interface being used as an interpreter, which allows me to use a "pure" implementation of that interface, if you also squint and allow the initial construction of the value to count as a "pure" call and the output of the test implementation to be considered as a pure output, as a test driver. This turns what superficially looks like imperative, highly stateful code that may even have extensive dependencies onto external state into pure code when used with its pure interpreter, which is great for testing. Then I can also write very impure, but highly focused, integration testing on to the "real" free monad interpreter to be sure it works as I expect, with its interface being minimized to just what the interpreter needs which makes the testing easier.

So when I sit down, in real life, with a real engineering problem, the choice I face is not "Write Haskell/Rust and be pure and wear the hair shirt" (as the saying goes) and "write imperative code that will bring the world down around my ears". It's between the first, and "write in an imperative language with guardrails inspired by functional and stronger languages that actually do a pretty decent job." As a result, the choice I face is not so much day versus night, but day versus "overcast but warm day". I don't deny there's still a difference and a value in the harder, stronger, more rigid languages, and there are absolutely still tasks I might reach for them for. But the value proposition the harder, stronger languages bring me are much more muted than they might be for someone else.

Effects are cool, I hope people continue to research them. I'd love to play with them sometime. Thumbs up to this Rust experiment too. But the simple truth is, they don't solve a problem I actually have right now. The "effects system" I implicitly get from already being careful with IO and my separation between IO-using code and the IO drivers solves my real problems, and nobody else on my team is reading my code and going WTF, because the value proposition is obvious after just spending a minute or two reading the test code.

Again, I want to emphasize, I'm not saying my solution is perfect and therefore anyone who uses any harder, stronger languages are wrong. I'm very explicitly saying the opposite. I'm just saying that when you include choices other than a binary "use the strongest, hardest language possible" and "be cast into the outermost darkness of pure imperative programming, where there is much wailing and gnashing of global variables", you may find that the ideal engineering balance isn't either extreme.

(Though I would say it's closer to the former than the latter. Undisciplined imperative programming is every bit the nightmare the functional advocates say it is, and what happens if you try to multithread without discipline hardly bears thinking about.)

All that said, I highly, highly recommend learning something like Rust or Haskell and becoming good at it. You can kinda sorta pick these principles up in other looser languages, but there is a ton of value in working in an environment where the compiler will rigidly enforce these practices. If those are still too strong, even Erlang/Elixir will give you a lot of practice, with a bit less rigidity. It is so much faster to learn in the stricter languages than in the looser languages. And it's a valuable skill that you may then someday deploy when you encounter a task where you need the full strength they offer.


"I am someone who is writing Go having been informed by the lessons from Haskell and Erlang."

This is what I saw with good Java developers 20y ago, E.g. controlling side effects and trying to distinguish IO and pure methods, in Java, 20y ago.


There really is nothing new under the sun. Below the furiously churning surface of the programming world are calm waters barely disturbed in the past 20+ years. And it's not all that far below, either. I've come to resist learning new techs not because it is hard for me at my age, but because it is too easy, and it is easy to get sucked into spending all my time learning the latest churny surface gloss on old ideas without ever exploiting a particular tech to do something useful because I'm moving on to the next churny surface gloss on the same ideas. Gotta actually build something at some point.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: