The perfect language and why Go still isn't it
snazz.xyz
snazz.xyz
After 20 years of coding, and having just recently discovered LISP and learned Clojure, I believe the perfect language will be a Clojure compiled to native with the ease of Go (e.g. w/ similar import system).
One such direction seems to be Carp (https://github.com/carp-lang/Carp), but I haven't tried it yet.
Let's name it. I wouldn't call either of these a "software renaissance". Docker is a wrapper on top of Linux kernel containers, and Kubernetes is a 10000 pound gorilla on top (and port of a C++ app, some say even through a Java rewrite/transpile to Go).
Go was just at the peak of being fashionable at the time (and had some basic features not semantics/syntax related like easy static compilation and cross platform-ness) so got adopted by these projects, the same way many new projects now use Rust.
I've written a 150-line container manager in shell before (because we had a use case where both Docker was not the right technical fit out of the box and the organization had barely any operational experience with even normal use of Docker), and it's great that containers are built into the kernel and you can just use unshare and a bunch of bind mounts if you'd like, but Docker is its own quite substantial product and brings its own approach to building systems (Dockerfiles, container networking, etc.) that aren't implied by or required by the kernel API.
http://www.smashcompany.com/technology/docker-is-a-dangerous...
There is a difference between Linux container (Docker underhood) and full blown Virtual Machine. In case of docker you are not running new operating system, you are just running new processes in different namespace but still using the same kernel. Linux containers are lightweight, you can start them as easy and fast as other processes, without overhead. VM (in most cases) is separate operating system running, which means startup time is different, you have overhead (CPU usage for keeping OS running and all its services, additional RAM usage, additional disk space usage etc) of whole operating system + virtualization overhead (in a lot of cases but can be minimzed).
Go turned out to be a pretty bad choice for container tech due to its awful multithreaded runtime. Things could have been different if it focused on a single threaded runtime or provided control over OS threads, but it did neither.
Also neither of those projects were enabled by Go. One is a pretty low quality but strategic push into the cloud and other one is a pivot from an attempt to solve real problems of building a PaaS hosting platform.
Can you please elaborate on this? My experience with Go is that it is able to fork/exec programs and manage cgroups via sysfs on Linux at least as well as any other programming language can.
Certain things like setns(mountns) will straight up fail in a multithreaded program, so there are some nasty hacks in runc to handle such things in C prior to the go runtime firing up.
I’m confused. fork()/spawn() are the proper means to launch child processes on Linux. What language would do things differently if the syscall determines the means here?
In fact, all namespace related operations are really annoying to do from Go because the runtime is always multithreaded and you have no control over the threading except for locking the active goroutine to its currently assigned thread. This also makes that thread unusable for any new goroutine (even goroutines that are spawned from code running in that thread), which has the side-effect that the new goroutine will always be in the original namespace context.
What you mean by that? What makes it a bad choice in context of containers ? I ask because I know how Go is using M:N threading model in its runtime and what linux containers really are, but I can't find a reason in which I would use word awful to describe using Go in this context.
There is runtime.LockOSThread(), but then if something happens to spin up a new groutine from there it will be done on a new os thread and not running in the container context.
It's not totally impossible to work around, however it does make many things incredibly frustrating.
Even stdlib spins up new goroutines.
Also with GraalVM you can equally support low memory, fast startup use cases.
( based on having spoken to hundreds of operators of both over the last few years, while (disclaimer) working for HashiCorp and other distributed system vendors, but also having personally run both at serious scale)
Getting into language or tool superiority arguments without deeply analyzing the environmental factors and use cases seems like a pointless exercise to me, and ought to be discouraged here IMO.
ZK is using Paxos and Etcd is using Raft. It's difficult to compare languages in this case as you will compare consensus algorithms mostly.
> Also with GraalVM you can equally support low memory, fast startup use cases.
How is GraalVM performance this days? Last time I've checked[1] it was a lot slower than JIT.
1. https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
(Atomic broadcast can be reduced to consensus, but frames the problem differently.)
C++ would be a lot more fun without the obligation to maintain header files all the time.
This is one thing that modern languages get right.
You can compare Go's gonum to Python's numpy. For Java's Weka, Scala's MLlib, Python's scikit-learn, C++'s Dlib to what Go is offering.
Maybe Go leans more on its C-interop for these sorts of things which is a bit like numpy.
Am I missing something? Will this change?
Since I am not finding what I am looking for in Go then maybe I should ought to try out Julia.
Thanks for the JuMP reference. That looks cool.
I think this has to do with golangs poor FFI performance.
I think it should always be mentioned when people call java fat, that the core language is usually fast enough. What makes java "fat" is the mentality of "Frameworks".
For me, Go seems like java, but without the fucking frameworks.
Needing to bring a very large, very complex virtual machine to execute your program adds a lot of “fatness” IMO.
And for those that want the free beer version instead, using a fat jar with linked jvm.lib is hardly different than packing Go's runtime in an executable file.
The idea seems to be there and "obvious" in a way, but haven't been able to pinpoint exactly what it is.
Is it a feeling or an actual metric?
e.g. When I work with Java in IntelliJ, things don't feel significantly slower than developing Node in WebStorm. Is it perhaps the way the code looks, in terms of verbosity?
I do agree that frameworks contribute to this feeling of Java, but again, is it because a Spring Boot webserver boots slower than a Node one?
Or maybe, say a Java cli program vs a C++ one. Is it the speed at which things run?
Could it be that, because we know Java uses a VM then it "should" be slower than compiled code and thus it's fatter?
What is that "thing" that makes us determine that a language is fat or light?
What exactly constitutes overhead is debatable, but generally anything that is not strictly necessary for the functionality is considered as such... Even if it has useful tradeoffs.
Examples from Java are the GC, compound types need to be objects and thus are wasting space with type and vtable pointers, and the jvm is actually an interpreter (one with only stack semantics at that).
Since C++ has none of that, it is quite natural to say that it is more lightweight than Java.
But that in itself is not necessarily a judgement about execution speed, although it is heavily implied. If you write Java in a way that it is heavily jitable and doesn't stress the GC, you can end up with a faster program than a naive C++ version would achieve.
But all else being equal, the C++ version will be ever so slightly fester, because less stuff to do means more stuff done.
So I was wondering if it was something that people felt differently, or if it was mainly the same theme, as it seems like it is.
Edit: that said, Java was created when we didn't have containers. (Just as Clojure was created before Java had lambdas and aggregate streams). So it may be the time to come back closer to the metal again, but as you point out, we need to maintain a clear mind while contemplating that.
In the same way the word modern is thrown around in the context of languages that have evolved a bit, I think heaviness is a notion that is an indicator of how committed the person is to the tradeoffs made by the tech: a heavy language made tradeoffs erring on the side of technical debt and the light one has not.
But it's easier to present one's own preferences as technical facts when trying to flame or troll.
In the case of frameworks, they often make choices for you, they are opinionated. It's easier to get unhappy about it, in a bike shed sort of way
For example: Kotlin is a better Java in almost every way.
The problem with almost all such languages is that they aren’t different enough from their “parent” languages, meaning they struggle to gain traction. Both D and Kotlin are here.
The Android success story might just be what the language needs to establish itself enough that it’ll become a backend staple too
Outside Android there are better options.
On the JVM, just like any other platform, is safer to bet on the systems language of the platform than guest languages with extra debugging layers, tooling and their own wrappers for idiomatic code.
Then Kotlin is trying to stretch too much, meaning that any portable Kotlin code cannot depend on any platform or needs multiple implementations.
Finally Kotlin/Native has special semantics for handling data structures, as it tries to be Swift like, so special care is needed when the code is supposed to be used from Kotlin/Native as well.
If you mean Swift's issue with having to use weak references to prevent cycles, Kotlin/Native doesn't do that – they have a cycle collector, so it should behave the same as Kotlin/JVM.
It does have a different threading model though, where you can't share mutable structures between threads. Very recently they've introduced a "relaxed" mode that does allow it, but it's extremely experimental and sounds like it would be slow (since it can't defer updating reference counts).
(Also, what are you referring to with "extra debugging layers, tooling and their own wrappers" for Kotlin on the JVM? I can use IntelliJ + Maven + Spring with either Java or Kotlin. I don't see any extra layers.)
Try to call idiomatic Kotlin code from Java and see how it looks like. Specially when co-routines and similar higher level constructs get added, or having to deal with compatibility between Java streams and Kotlin sequences, which don't build on top of Java ones.
When Loom arrives, it will be a similar story, Java proper fibers and Kotlin co-routines and how they might interoperate.
Nothing unique to Kotlin per se, all guest languages happen to have similar bumps. Calling Scala or Clojure from Java leads to similar lists.
Then one is required to buy into JetBrains tooling for Kotlin, and get an additional license for Clion as means to have a graphical debugger for Kotlin/Native.
There is an Eclipse plug-in, which is kind of 2nd tier, and none at all for Netbeans.
With Java it is just ready set go, no matter which IDE, with just one IDE for Java and native, alongside mixed mode debugging (a feature JetBrains doesn't see a value supporting) and not all Java shops are into InteliJ idolatry.
So, could it be more of a reaction to java-for-android being sub-optimal or the fact that Google suggests kotlin to new developers than Java itself being sub-optimal for the task?
I genuinely think D and Rust are the only remotely interesting and/or good systems/non-functional languages around but lifes not a meritocracy.
But even then, if I want to have fun and feel like I'm learning new things while I code, I'll always use something else that's more interesting.
[1]: https://elixir-lang.org/blog/2019/06/24/elixir-v1-9-0-releas...
We are pretty new at this, we are still trying to figure out a lot of things. We are still experimenting a lot with many features. But step by step, new language after new language, we keep improving.
Trying to be realist tends to be a painful exercise, but in the case of programming languages, I think the situation is looking pretty good. There might be a billion paths left to explore, but at least the ones we are exploring are bringing something useful to the table. I hope we soon start writing articles titled "the perfect languages and the exciting ways we are getting closer to them". The article was pretty good, by the way.
So I'm basically asking for OCaml with multithreading.
The perfection, however, is not something Go never meant to be. The perfect language possibly support fully dependent type like Idris while maintains zero abstraction cost like Rust, having precise syntax rule like Lisp, and a powerful runtime like Erlang, and the list goes on.
The success of Go lies in it's trade-offs, the designers are opinionated, knowing well what they want and what they don't, and executes things well in the implementation.
If they thought it mattered much, they'd change it... But since the code should be formatted correctly always, it's not necessary.
Also, while it's said comically often in HN comments, Rust (and particularly the iterator api) is very pleasing to write. I'd say it sits in quite a sweet spot language-wise.
Context, I'm a C++ programmer who had been writing in dynamic languages for the last couple of years.
`interface{}` is the equivalent to using `Object` in java land. It completely punts on any sort of type safety, relying on the developer to handle messy edge cases correctly. I think one of the only reasons that it hasn’t been as severe a problem in go as java is thanks to that explicit error checking that go makes developers do (and that gets so much hate online), forcing developers to consider the case that the type isn’t what they assume.
But genetics provide a useful way to safely make those assumptions, and cut down on the boilerplate around them.
From my experience, interface{} is incredibly uncommon in application code. In most instances where the standard library uses it, it's usually justified because the function in question really accepts literally any type of argument, e.g. json.Marshal() or reflect.TypeOf(). The only counterexample I can think of are sort.Sort() and sort.Slice().
Given all the implications of adding generics to the language, I think Go would be better off just adding the common higher-order list/map manipulation functions to the builtins, i.e. the likes of map(), filter(), maybe reduce().
I just wanted to make some small functions for logging different types of things. I wanted to use parametric polymorphism to dispatch to different functions depending on the type of the thing I passed in. I think I ended up creating a few `log_type_foo` and `log_type_bar` functions rather than the single `log` function I wanted. It looked really ugly to my eye.
The thing I really wanted was parametric polymorphism. I seem to remember reading something where the go people think this is equivalent to generics.
EDIT: clarity
func DoWorkWith(foo Loggable) { ... }
type Loggable interface {
//for example
LogTo(os.Writer)
}If your look how much changes are proposed as part of the Go2 work, I'm not sure the Go developers agree with you.
Also, trying to bend a certain language to adopt features and functionality that are unwarranted for it's use cases is another reason for the criticism some languages draw upon from the community, therefore it's best for the language to stick to what it does best and stop it at that instead of trying to fit in to uses which it was never intended for when it was first developed. But the language itself is agreeable to evolve through time, but to constrain itself to it's actual purpose would be better for both the language and it's users.
Even languages that supposedly no one uses like Fortran and Cobol, get their standards revisited every couple of years.
One might not want to re-write that surviving Cobol program into a reactive text UI with a couple of FactoryFactory classes, but it is surely possible as per latest language features.
So there is no "perfect" language. All languages have some trade-offs. If you want small - you pay for it.
Taken to the extreme, the result can be an extremely small language. If your language is expressive enough, the result can also look like it is a much larger language. For example, Forths implement if/else, for/do, while/do, repeat/until, switch/case in terms of some stack manipulation and ‘jump’. Similarly, Common Lisp builds its control structures, object oriented programming support, etc, out of a few primitives.
t, u := x | λx.t | (t u)
Small language (three production rules), definitely universal.
Of course the semantics is a lot more complicated than most people expect. But it IS syntactically very simple
common lisp syntax. Even simpler, but that shifts much of abstraction capabilities to the operational semantics.
Also make some popcorn and enjoy the Twitter flame wars while you're at it.
There are plenty of small languages, but they lack popularity or rich libraries. The only language I can think of that remains in widespread use and is fairly small in language size is C.
I'm strongly in the camp that small languages are preferable to large ones and wish language designers would follow this principle more closely. But small does not necessarily mean more readable. And language syntax design remains a neglected aspect of programming language design.
And if we consider the amount of compiler specific C extensions, across all C implementations, it gets even bigger.
http://www.quut.com/c/ANSI-C-grammar-y-2011.html ← 2010's C (536 lines)
A language grammar is only a tiny part of what makes a programming language.
Compare ISO C pages and C compiler manuals instead.
It's FreePascal. Nobody believes me, but we have perfection already. No crap you don't need. No crazy corner cases. No C++ templated metaprogramming lambda auto pointer garbage. No "I can't write a linked list without a Grimoire" Rust.
It's great. You get a ton done, and simply ignore the language wars. No VM trash (Java). No web trash (JavaScript/"Webasm"). No crap.
Try all the others, and try FreePascal. It's old enough that people aren't bolting on stupid shit. The community is awesome. Everybody just gets shit done. There's a lot of art if you go looking, but none of it requires a kilopage book to describe.
Delphi only supports it for COM and Objective-c/Swift interoperability, everything else is as manual as TP for MS-DOS, and I bet FP hasn't improved in that regard.
The successors of Pascal are also highly interesting. I used Modula-2 quite a lot, but never got deep into Oberon. I do think one big advantage of Go is, that it so strongly draws on the Wirth languages that it almost can be considered a modern successor.
And I fully agree, one shouldn't use a language these days for general programming without some kind of automatic memory management. Which is the other aspect which drew me towards Go, a nice nativley compiled language with a GC.
I was initially also drawn into Go, mainly due to its Oberon-2 influence as well. Even did a couple of contribution attempts during pre-v1 days , but they weren't that good anyway.
However back in the Oberon days, what I liked was the evolution that followed suit, Oberon-2, Component Pascal, Active Oberon and finally Zonnon.
As such, I never appreciated the minimalism discussions around Go and not having modern language features. For that we already have Wirth cutting down Oberon features in every Oberon-07 language specification document revision.
Still, I do look forward for some Go 2.0 roadmap items to actually land in Go, and advocate for its use in applications that would be otherwise written in C, if Go wasn't a thing.
And I also collect examples of actual systems work done in Go, regardless of it not being suitable in the opinions of anti-GC crowd.
I am certainly watching the work on Go 2.0, it is certainly worth to continue working on enhancing the language, but I am very happy how carefully it is done as adding too much complexity to the language would be detrimental. There are only a very few things I miss generics for and usually they can be worked around.
I might have to add the disclaimer, that my paying job is mainly about programming in Lisp, so for me there is a high-level alternative to Go, but this also shows me how Go is a great solution for a lot of problems.
However that isn't Free Pascal, which was the OP's point.
Having said that, RemObjects Oxygene is probably an alternative then. :)
The first is that once a user reads over the various functional things like let statements, record types, union types...etc, you still don't really know how to code in F# unless you already have a .NET and in particular C# background. Nearly all the documentation and books (I have 3 of them) assume you're a C# dev making the switch. This is similar to learning Clojure/Kotlin/Scala on the JVM without knowing any Java. Everytime I post this someone tells me that is untrue, so I give it another shot and an dissapointed. With Python, there are dozens of books written for beginners that show you the building blocks (lists, control flow, dictionaries, tuples, functions, classes, file IO...etc) and how to use the blocks to build useful programs. With F# you get three sentences in and then hear how this is just like this thing in C# that I also know nothing about.
Microsoft also seems to not be very dedicated to providing F# tooling support.
On the plus side, I've found the community to be super smart and helpful and the language itself is very beautiful from an aesthetics point of view to me somehow (just looking at a bunch of code ). In comparison to C#, there is sooo much less noise (); everywhere.
Go is a pretty simple language that compiles to static binaries. F# ain't that as there is all the bloat and baggage of .NET. Even with .NET core, that is more confusion for me.
On the tooling front, Microsoft include it as a prime language with C# and VB.NET in dotnet core, and ship F# tools with their latest IDEs, which I appreciate and is almost more than I would expect, since their flagship is C#. It would be nice if it was better, but that would require it to be a lot more popular which is fair enough.
Static binaries are something that would be real nice. I've done it with F# using CoreRT, but its certainly not as simple as Go.
CLR is multiple language runtime and actually the only language that has full control over all possible bytecodes is C++/CLI, not C#.
CoreRT is on its way out, going to be replaced by Crossgen.
They did a talk on AOT compilation at Java Language Summit last week.
I've thought OCaml might be a good F# replacement as it has its own compiler and interpreter that doesn't need the CLR/JVM and F# was mostly based off of OCaml, but it's a little awkward to me on Windows and OCaml itself seems to be a little painful (more boilerplate than expected) for doing things like IO (which I do a lot of).
Not the newest, but J.H.'s books are great: http://www.ffconsultancy.com/products/fsharp_for_technical_c... (I particularly like the old spiral-bound F# for Scientists.)
F# is pretty close to a first-class citizen in VS and VSCode (Ionide, etc.) at this point and the community seems good -- what support are you lacking?
Which, like, fair enough. It's not perfect. But it's still really really good. I mean, people still use Java...
A more ethical approach is to simply learn at work during downtime over browsing the web. It even looks like work, so few people complain.
Also, writing this type type of software makes you an actual 10x engineer (by making 10 other engineers more productive).
What would a REPL bring to the party?
It's nothing like unit testing. It's like running around in the system able to poke-prod things as you go.
Particularly important is that it doesn't mean working in a console. Most REPL-based dev is done in the source code file itself, and you send code from the document to the REPL process. Code is written in a file as usual, and you utilize the REPL to massage the system as you want.
Though it's Clojure-based, you might want to watch this talk: https://vimeo.com/223309989
I meant that I use tests as a way of poke-prodding things - because Go compiles so fast, and you can run individual tests, it's easy to tool around in test code setting up specific state in code and seeing what happens.
When someone says "I want a programming language
in which I need only say what I wish done,"
give him a lollipop.
- alan-j-perlisIt's the economic attributes of a language that contribute to its market share. Go is a very good language from an economic point of view. (Java was also a remarkably good language, from an economic point of view.)
As to the your specific point, I suggest a sufficiently large slice of software workers will include the sub-set that write long articles defending their theoretically so-so but economically superior languages: Those blog posts are a feature, not a bug, of economically viable programming languages.
The bottom line is no one has proven that "the bottom line" is positively affected by use of sophisticated languages. The killer app of ho-hum languages is their demonstrated economic value.
Those attributes have nothing to do with why it's a garbage fire, and 'sophistication' also has nothing to do with it. You could redo PHP to be equally sophisticated, and still have the things that make it popular, but also be a hundred times more consistent and less buggy. Heck, fixing refs and inconsistent syntax could make it less sophisticated.
At least they've been fixing some of it...