Hacker News new | ask | show | jobs
by correct_horse 27 days ago
Haskell, OCaml, Erlang lead the way and Rust, Zig and Go get all the mindshare. I feel like its a common pattern for more experimental languages to pioneer features and other languages to copy the features and bring them to a C style syntax that the majority of devs are familiar with.
5 comments

I wish I could find the reference, but there was a great blog / article by a computer science academic basically saying that OO, procedural, and functional paradigms are extremes of a design space where the “middle” of its Pareto frontier was essentially unknown until recent advances.

Moreover, many functional languages are getting pseudo-procedural features via the like of “do” syntax and monads, but that this is in some sense a double abstraction over the underlying machine that is already inherently procedural.

Starting from a language that is already procedural and sprinkling some functional abstractions on top is simpler to implement and easier for humans to use and understand.

Rust especially showed that many of the supposed advantages of functional languages are not their exclusive domain, such as sum types and a powerful type system.

Update: Hah! ChatGPT found it: https://news.ycombinator.com/item?id=21280429

Note the top comment especially, which explains succinctly why functional has rather substantial downsides.

Rust and Zig brought new ideas for memory management that Haskell, OCaml, Erlang sidestep having garbage control. its honestly amazing to me that they managed to get the adoption they have while being so innovative. I say this as a fulltime elixir dev.
What new ideas did Zig bring to memory management?
Pluggable allocators. Allocators are passed to functions as arguments, the caller decides exactly how memory is managed. Maybe not "new" in idea, but "new" in being consistently applied everywhere in language & libraries.
Already Turbo Pascal for MS-DOS had a custom allocator API for its runtime.

All modern C++ collection types have allocators as type parameters, and this was already a thing in the compiler frameworks like OWL during the 90's.

Zig makes allocation an explicit runtime concern:

   var list = std.ArrayList(i32).init(allocator);
where allocator is a runtime value implementing the allocator interface.

   fn parseJson(allocator: Allocator, input: []const u8) !JsonValue
Zig forces the caller to decide since allocator is part of the function contract. And this is consistent everywhere in Zig. Effectively speaking, this is universal, explicit & mandated dependency injection for memory management as opposed to to classic C++ STL allocators.

Sorry sir, I have a nostalgic fondness for C++ - it was my first language, but it just doesn't compare for flexible allocation convenience compared to Zig.

Fact remains it wasn't the first, and it remains to be seen how market relevant Zig will ever turn out be, especially after AI driven coding became mainstream.

The irony to argue about manual memory management ergonomic, when code is written by agents.

You would be surprised to see, how OCaml and the BEAM specifically think about memory.
Zig is Modula-2, Ada 83 with a curly brackets, what new ideas?
Being first isn't necessarily good if you get it wrong, though. Laziness by default and Hindley-Milner type inference seem like mistakes that simply aren't going to get cleaned up. Other languages make their own mistakes too.
What's wrong with Hindley-Milner?
Leaving out types in public API's makes type errors hard to understand. Types should be declared in the API and bidirectional type inference used in the implementation.

https://jimmyhmiller.com/easiest-way-to-build-type-checker

Eh. This causes some problems for rust. Right now you can have a function return impl Trait instead of a concrete type. Very handy - and essentially required by async functions.

But the language also requires that types have names in lots of places. For example, you can't store an 'impl Trait' in a struct. You can't make a type alias of an impl Trait. And so on. As a result, async rust can only interact with a butchered subset of the language. (You can work around this with Box<dyn Future<...>> but performance suffers.)

There's a proposal[1] to fix this. But the proposal has been under discussion for (checks watch) 7 years now. Until this lands, async remains a second class citizen in rust.

This entire problem stems from rust's early decision to requiring concrete types at interface boundaries.

https://github.com/rust-lang/rust/issues/63063

> I feel like its a common pattern for more experimental languages to pioneer features and other languages to copy the features and bring them to a C style syntax that the majority of devs are familiar with.

You're ignoring the fact that it's harder to bring new features to older languages as they have more bloat to deal with as not every idea turns into a success. So younger more focused languages can iterate more quickly. Also, being willing to make breaking changes makes things easier. Microsoft tries hard not to do that with C#.

Over time, maintaining any software becomes harder, languages are no exception. The fact that c# is still around, and still being developed is a feat in itself

The idea that Erlang is experimental is pretty amusing- it’s one of the most stable platforms there is.