I do not think this shows that Java is a simpler language, if anything it shows that it has more features (ie. being more complex as a language) that make it easier to match what you want to do.
Java might be simpler (easier) to use, but that doesn't make it simpler as a language itself.
For a more extreme example take C vs C++ - C is certainly simpler as a language than C++ and nobody would ever claim that C++ has any relation to simplicity as a language whatsoever, but a lot of things are much simpler to do in C++ than they are in C because of all the additional features C++ provides.
Thankfully there are two different words with very separate meanings that people nonetheless tend to conflate [1]. "Simple" means "not many features", and "easy" means "good to get things done with".
[1] Maybe because we have lots of people speaking English as a second language? In my mother tongue, German, there is no distinction in vocabulary between "simple" and "easy". Both translate as "einfach".
Though if we want to make the clear distinction, like in the Clojure talk below, for the programming domain that’s fine.
I'm not sure about this. What utility is a language with only simple constructs that is hard to use.
Not quite a counterpoint, but a thought in that direction is:
There can be features that make it easy to implement something which some find hard to understand.
I suppose I question the value of simplicity for it's own sake in a language and would rather defer to how simple and understandable the resulting code is.
I frequently consider the added complexity cost of a more complex compiler inherent complexity because I find the simplicity of use dwarfs compiler simplicity or simplicity "just cuz".
Sure, you may not be touching those during your web development, but that’s because you don’t know how to have fun and outsourced it all.
> it would be a tad slower
a tad? I'll use C++ as an example, but the last time I profiled a hash map in C++ vs C#, the C++ lookup took 1 nanosecond[0] and the C# version took 1ms (1000x slower) (I don't have these metrics anymore but I believe it was one ms or slower, and Google seems to confirm that).
Now, if the difference between C++, a machine compiled language, and C#, a runtime compiled language, is 1000x for a very common operation, how much slower will an interpreted language that is running at an even higher level be??
The reason 90% of our apps are such slow trash these days is because programmers think that there is no difference in a couple milliseconds when it is a huge difference if that couple milliseconds is being run thousands of times through hundreds of thousands of lines of code! Take a look at how fast the Jai compiler is compared to a modern C++ compiler. Or even a C compiler vs a C++ compiler. It's night and day because of how much cruft slowly gets added on.
So I find your claim that most of the Unix utilities are only written in C because that was mainstream in the day highly dubious. If anything, they would be written in something like Rust today, but definitely not JS or Python.
Edit: I would also like to point out how much of a pain it would be to write low level network code, like what's necessary in curl, in a language like Python. When you're writing low level net code you want as little interference between the bytes and the programming language. Because if your bytes have some hidden padding, or the endianess is swapped, or a data structure is aligned a byte off, it means the difference between between correct transfer over the net, and sending garbage over the net. Python is not conducive at all to understanding how an object will be transformed into q series of bytes, whereas C is almost a direct translation from a struct to a series of bytes.
[0]: https://gist.github.com/ambrosiogabe/66a6e2fdc77e6a600e570f4...
Regardless, the benefit of not using C++ is and always has been: Not having to use C++.
C# benchmarks on my machine. FastFindRecipe just does a hash set lookup, it looks like this:
private static readonly HashSet<CraftingRecipe> FastList = new HashSet<CraftingRecipe>(new CraftingComparer());
public static CraftingRecipe? FastFind(CraftingRecipe r) => FastList.TryGetValue(r, out var res) ? res : null;
| Method | Mean | Error | StdDev | Min | Max |
|--------------- |---------:|--------:|--------:|---------:|---------:|
| FindRecipe | 315.2 ns | 1.17 ns | 1.09 ns | 313.6 ns | 317.4 ns |
| FastFindRecipe | 124.5 ns | 0.23 ns | 0.21 ns | 124.1 ns | 124.9 ns |Two orders of magnitude.
If you have to have multiple structures that need to be precisely laid out in binary, python is the wrong tool for the job. Delivering content using Tcp or Udp packets requires very explicit binary definitions of the data your sending. Sure you can use a wrapper on top of a library that deals with the low level stuff, and that's what Python is all about.
Use the proper tools for the proper jobs. If you're using C to write one off scripts or simple data manipulation like analyzing csv contents or something, then you should probably use a higher level language instead. If though, like curl, you have to be very explicit about the binary description of objects to accurately send and receive those objects between a network, then use a low level language.
> runtime isn't the constraint that we're hitting anymore
If you're sending data over the network you're absolutely hitting this constraint. Reading some data from RAM is on the order of like nanoseconds, sending data over the network is on the order of hundreds of milliseconds depending on the size and location you're sending that data. When it comes to sending data over the network, literally every byte does matter.
It seems like you were advocating using a higher level language for low level, systems programs. If that's the case I disagree. My original argument was about Unix utilities. Unix utilities contain everything from "list the files in a directory" to "compile this C program". The original poster was talking about how Curl would probably be written in Python if it was written today. This is just incorrect. From the author of curl[0]:
>> If I would start the project today, would I’ve picked another language? Maybe. Maybe not. If memory safety and related issues was the primary concern I had, then sure. But as I’ve mentioned above there are several others concerns too so it would really depend on my priorities.
> If you're talking low level microcontroller stuff, or bit banging, sure, do it in C if you like.
This is the argument I'm making. Unix utilities are usually dealing with low level bit banging stuff. Unix utilities are also meant to be extremely portable. C is the glue that holds all programming languages together, whether we like that or not. So programming a library in C automatically ensures that the library can be used in practically any higher level language.
The ratio between 1 ns and 1 ms is a million.
https://floooh.github.io/tiny8bit/
https://floooh.github.io/sokol-html5/
https://floooh.github.io/visual6502remix/
The common pattern is though that both the C and C++ stdlib are mostly useless for this type of project, you need cross-platform libraries where 'cross-platform' also includes peculiarities of the web browser runtime, specific language features are (mostly) irrelevant in this case - except maybe for the bit twiddling thing in the emulators where C is clearly superior to JS (plus you get lean native versions "for free" without requiring a heavy Electron runtime).
For example, I consider most multi paradigm languages complex because in them functions, variables, objects, control flow, etc. are fundamentally different concepts.
But something like LISP, smalltalk or Haskell are simple in my eyes, due to almost everything being the same concept on a fundamental level and the other stuff being built on top of that concept.
Conway's game of life has an incredibly simple definition, and is Turing Complete. The Brainfuck language has a minimal number of language concepts and barely any syntax. And yet, programming in these languages is very hard.
Humans need a certain level of language complexity to be able to express themselves. When you simplify things it's not always clear if you're removing superfluous complexity, or removing tools needed to deal with inherent complexity of the problems you need to tackle.
I don't think there's even any single objectively appropriate level of complexity, because it depends on the complexity of the problem the language tackles. By analogy to human languages: scientific language has its own jargon (sometimes overused!), but it needs an extended vocabulary to precisely describe the things and problems it works with. Without the added complexity it'd be unproductive due to being verbose and sometimes ambiguous, like the amusing example of Up Goer Five: https://xkcd.com/1133
Specifically what I wanted to express, is that, for me, the turtles all the way down is what I understand as simplicity.
And yeah, simplicity alone can't be used as a value judgement.
In practice though it doesn't give you any of the tools you need to do anything except make tools to do those things. Which appeals to yak-shavers like us for sure. But if you need to sit down with it for months at a time and just get work out the door it fucking sucks.
Also 5.1's GC is goddamn tragic. In practice you're usually at the mercy of whatever was implemented by the embedding runtime, but if you're ever left alone with just the raw 5.1 GC you'll wish you weren't. Later versions improved that dramatically.
> you're usually at the mercy of whatever was implemented by the embedding runtime
Are we talking about the same Lua? The engines I know (PUC Rio and LuaJIT) are not at the mercy of embedding runtimes, but haver their own, sufficiently good memory management.
I'm not an expert user of anything though, or building tools. My professional experience of it has been using ancient versions that are embedded into C or C++ systems to build simple guis and automation within those systems. I don't think it's well suited to this, but this is a common (I believe even it's original?) use case!
I've never used luajit, or any version where I had full control over the runtime at all. I understand people love this language and I'm not saying they're wrong to. But for the things I've needed to use it for it has not been enjoyable at all, particularly 5.1 which is the one I have the most experience with by far.
> I'm not an expert user of anything though
But apparently confident enough to make negative comments about the technology.
I might be wrong about the technical reasons why, and I'm genuinely sorry if I've unfairly maligned your baby here. But it has always been unpleasant for me and I think I'm entitled to my confidence about my own experiences in it.
I have, yes, it's great. No complaints, would choose LuaJIT again.
I do regret that the quad-color collector will likely never be implemented, I'd content myself with a back-port of the new generational collector, but the allocation sinking in LuaJIT has kept me out of trouble for the most part since it can put 'tables' on the stack if it can prove they don't escape.
The main practical issues I've run into with 5.1:
- per-object overhead in time and space both increased significantly, making many small allocations much more expensive than fewer big ones. further pushing you into the "mutate a few huge global tables" pattern that is already a problem in lua conventions.
- some of lua's more lauded and genuinely powerful features, esp metatables, don't work smoothly or predictably with the GC. it leads to people trying to manually tune it with __gc metamethods, which is hard to do correctly and causes memory leaks if done incorrectly.
- weak tables idk,. esp with strings for some reason. similar to the __gc, there is a solution there but in practice it's very hard to be sure you've got it right and it's worse to get it wrong than to not try.
I'm sure a PL expert can explain and justify all this but I am just a normal person who codes for money. Lua is admired for being simple and accessible, but especially in the older versions you get into the low level weeds real fast over this stuff. Kind of a good example of minimal implementation exposing you directly to irrelevant complexity, really.But much less high with the lambda calculus than with the other two; Haskell is not far from being a layer of syntactic sugar on top of the lambda calculus.
For an example of a non-trivial lambda calculus program, see this self-interpreter [1] for binary lambda calculus [2].
Point being, nobody wants to model their data structures as pure lambdas; even primitive integers are an addition on top of the lambda calculus.
> For an example of a non-trivial program
I don't think I'm taking the point you wanted me to from this program. It's under 30 lines, yet I'd need to pore over this for twice as many minutes to comprehend what it's doing. I can see the lambda abstractions and applications clear as day, but what they're doing I couldn't say. (The comments clearly say it's a universal binary LC interpreter, but we don't usually judge languages by their comments.)
EDIT: Right, okay, it's a submission to the the International Obfuscated C Code Contest. If it's obfuscated, then indeed, I'm not meant to comprehend it easily. Not sure that sells the lambda calculus.
Not that much less, since you can manually translate back your Haskell code to lambda calculus. E.g. your
List a = Nil | Cons a (List a)
rewrites to let
Nil = \n\c. n
Cons = \x\y. \n\c. c x y
in ...
Of course the type system is what helps you write correct Haskell code in the first place, so the untyped lambda calculus will be painful for debugging wrongly typed code.Also, in lambda calcalus there is no recursion built-in. You have to build it yourself with the Y combinator.
let Y = \f. (\x. x x) (\x. f (x x)) in program-using-Y
which is the slightly more legible version of
(\Y. program-using-Y) (\f. (\x. x x) (\x. f (x x))
I had a misfortune to deal with more than 200K lines of Lua code back in time [1]. It seems that my experience doesn't necessarily match those of other big Lua users though; it does seem to work when everyone is willing to invest in self-contained environments. In the other words successful Lua users doesn't exactly use Lua, they use their own programming systems based on Lua. (I should note that such systems are not guaranteed to be successful, as evident from my case.)
It can be done, but it requires a lot of discipline.
You might enjoy Teal, a sort of "Typescript for Lua" https://github.com/teal-language/tl
I don't think it's really missing features compared to Python or Javascript that would help with this. Tooling could improve a lot though :)
Adobe Lightroom?
In a similar way, even though the lambda calculus, SKI combinator calculus, or and MOV-only assembly are minimal in very real ways, the cognitive burden of expressing yourself in these languages is very, very high!
To pick a friendlier example, I'm a big fan of Lua. Lua is deeply minimalist, and from what I know, its developers try hard to keep its feature set orthogonal (and it has my favorite language feature, coroutines!). It's a great fit for certain domains. Nonetheless, it's not a great fit for most domains; for instance, I never got the sense that it could scale much beyond one-person projects.
General-purpose computing languages are in a bit of a catch-22. People need to do such wildly varying things with them, that in order to be minimalist, they must necessarily put some distance between the features they provide and the use-cases they are applied to. Conversely, to be expressive, they must minimize the amount of encoding required to take ideas in the problem domain and represent them in the language.
A domain-specific language (with high expressivity for the problem domain) invariably tends to take on general-purpose features, as users' needs expand and they push the language to its limits. A general-purpose language (with low expressivity for most problem domains) tends to take on more and more high-level features, to help bridge the gap between the language and the domains it is most often put to use in.
I think a polyglot environment makes a lot of sense. Use high-expressivity DSLs to serve your business logic, and high-generality languages to serve those DSLs. In some ways, that's the ideal of Lisp: your DSLs are indistinguishable from the host language itself, so the experience is seamless. But folks don't seem keen on polyglot environments, Lisp or not. So we're at an impasse.