As someone who basically started coding with C I feel the other way around, unsurprisingly. Even in scripting languages when I write something non trivial I tend to encapsulate everything in functions and then have a `main()` call at the bottom of the file. I think it's one of the things python got right with the `if __name__ == "__main__":` idiom that lets you import any script in a REPL to test individual methods for instance.
(Although the actual syntax of `if __name__ == "__main__":` is IMO utter trash and it amuses me that a language so obsessed with getting rid of symbols and looking like pseudocode decided that such cumbersome, obtuse and ugly looking boilerplate was just fine.)
This reminds me of when I tried learning Ruby. The tutorial I read started by talking about how elegant and beautiful Ruby could be, and how easy it is to read and understand Ruby at first glance, even if you’re new to it. Then they explained that the toString method in Ruby is called `.to_s()` and that lambda arguments were surrounded by pipe characters and I was like “wat”.
100 times this! I have always wondered about that.
Which is acceptable. Python is a valuable tool I use on a daily basis. But I find it annoying when Python programmers are snooty about "Pythonic" code.
As a compiler author, there are a bunch of nasty surprises to this approach. If you execute a file line-by-line, then functions only exist once you "reach" them.
If you write:
def a(): b()
a()
def b(): ...
...then a() needs to crash when first called, because b() hasn't been declared yet. So your functions need to be invoked via some kind of table, and can't easily use jumps to hard-coded offsets. And b() can't be inlined.There are dozens of these problems that come up when generating efficient code. And the easiest way to fix them all is to make your entire program "exist" from the beginning, so it can be compiled and optimized as a whole. Which is how you wind up with main().
But I typed the name of your website into the URL bar, and even stupid Firefox just hung trying to load or trying to redirect from what it thinks should be the default https version. Had to specify http:// to load it. The browsers are making it almost impossible these days to just go to a plain website. half the time they loop themselves into a frenzy and cache the failed address with predictive type to the https version somewhere a normal user can't clear it, even when clearing history. It's a bloody mess for anyone who still needs to build http services.
Cannot reproduce.
The only problem(s) I'm seeing are related to operator error; nothing to do with browsers "loop[ing] themselves into a frenzy". The server at www.randomhacks.net works, but the server at randomhacks.net, on the other hand (and which is not the same as www.randomhacks.net), is for whatever reason failing to respond to the HTTP request. Reasonable guess: it's not a Web server.
At best, the service operator has the server (arguably) misconfigured, and the client operator is expecting the browser to do something it shouldn't while blaming failure on the browser after assuming it's doing something that it isn't actually doing.
Yes, the entire setup is ancient. The www site is a static using CloudFlare, which (IIRC) needed a CNAME at the time, and CNAMEs did not work nicely with "bare" domains. The bare domain pointed to an actual server which did stuff, including serving a redirect on port 443. But that server is gone now.
I could fix this but it's way down the list after several major home projects. Besides, I come from an era where it was assumed that the www host was a dedicated machine. ;-)
Why would it “need to” crash? If it is easier to implement to not crash and the developer intention is clear why would you define your new programing language such that it “needs to crash” in this situation?
It is as if you go to your garden to pick tomatoes, but you trip over a rake you intentionally put in your way and then as you lay on the ground hurting you conclude that it is impossible to pick tomatoes. Picking the tomatoes in this case is making a compiled programing language which has a main file instead of a main function. And the rake you trip over is the intentional decision to make the program crash in this situation. Just don’t put the rake there (choose to not crash in this situation) and then you can pick the tomatoes (have a compiled language with a main file instead if a main function).
There might be more complicated reasons why it is neccesary to have a main function, but this example does not really demonstrate it for me.
Well, let me come up with another example:
def a(x=SIZE): ...
a()
let SIZE = 16*1024
When we call a(), we need the value of SIZE to provide the a default value for x. But SIZE isn't computed yet. We could try to "hoist" SIZE, but normally that just means we have: let SIZE = undefined
def a(x=SIZE): ...
a()
SIZE = 16*1024
And sure, you could invent a rule to "fix" this case, too. (It depends on how you implement default arguments efficiently.) But next week, you'll encounter another headache, and another. I've literally been through this a couple of times working on LISP compilers. "Executional" semantics are common in custom Lisp dialects, and it's a huge amount of work to get them right.The price you end up paying is lower program performance and higher compiler complexity. Oh, and importantly, you normally wind up with slower load times. A compiled program is "ready to run", and can be loaded efficiently using mmap() and maybe some linking. But a program where you "execute" the top level needs to run all those top-level definitions on each load. So then you're like, "I know! I'll write a heap dumper/undumper", aka "unexec". Which will fix this problem but cause 5 more.
And so it goes. This is one of those ideas that seems clever but leads to bitter regrets, at least in high performance languages.
> And sure, you could invent a rule to "fix" this case, too.
I would just do the simple thing. If X is not already defined when it is used in the function signature I would just give a compile time error. Easy to implement for the compiler author, easy for the developer to understand and rectify.
> But next week, you'll encounter another headache, and another.
I believe you. I guess it is one of those things one has to see for themselves to really appreciate.
>> JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code.
>> Hoisting is not a term normatively defined in the ECMAScript specification. The spec does define a group of declarations as HoistableDeclaration, but this only includes function, function*, async function, and async function* declarations. Hoisting is often considered a feature of var declarations as well, although in a different way. In colloquial terms, any of the following behaviors may be regarded as hoisting:
>> Being able to use a variable's value in its scope before the line it is declared. ("Value hoisting")
>> Being able to reference a variable in its scope before the line it is declared, without throwing a ReferenceError, but the value is always undefined. ("Declaration hoisting")
>> The declaration of the variable causes behavior changes in its scope before the line in which it is declared.
>> The side effects of a declaration are produced before evaluating the rest of the code that contains it.
So basically all the shit we take for granted when not writing C/C++. What is curious to me is whether the main reasons for not attempting this in a C++ compiler in 2023, or else moving the language spec in this (unofficial) direction, is to produce maximal performance-optimization, or if it's mostly a cultural thing at this point. It does have the benefit of turning away hordes of javascript kiddies from the gates, but, the relative value of the performance edge to any optimization is getting weaker as compute becomes cheaper.
Line-based stuff like BASIC's GOTO/GOSUB was kind of a fun workaround to the idea of even having functions at all, and I'd happily live in that place still... but... as somoene who manifestly does not write compilers, is it still an extravagant demand in this day and age to ask a compiler to check and include/throw for all the functions out-of-order before running a code file from the top?
[edit] What I mean is, this was fundamental to ES3/4 bytecode compilers for VMs like Java or Flash, and it would be absurd to ask e.g. Javascript coders [edit: said Python, don't work with Python, there are cases in PHP where it's necessary] to order their functions in the order they're invoked with virtuals to place them. This is like providing toilets on a cruise ship. It's barely even a "service", like whereas garbage collection or something is an actual service. Restructuring the order of function definitions as you build out dependencies has got to be one of the worst wastes of coder time I can think of. Great if you really can find an optimization by doing that, but the way people code these days that basically never happens. We have to assume that everything we write gets compiled anyway, or why else is it in the code! If there are no dead branches, what's the purpose of not "existing"/let's say pre-virtualizing all the branches, regardless of which order they're written in? (Serious question).
You couldn't naively make a forward reference to label. This is because wasn't defined yet at the time the assembler encountered your instruction.
That's when you learned about multi-pass assembly.
After all nothing stops you from writing in your language definition that defining a function anywhere in the file is the same as defining it in the beginning, and then you can treat it as if the entire file was in a main function, except for functions and the declaration of global variables.
def a(): b()
a()
import random
with open("max", "r") as f:
m = int(f.read())
b = (lambda: "foo") if int(input("Pick a number: ")) % m > random.randint(0, m) else Noneit becomes problematic if a function definition is conditional. i suppose then that the compilation should fail because i guess conditional definitions would not be covered.
The trickiest thing is that main() is not even the bootstrap function. The actual entry point of a program is usually generated by libc, and is called generally called _start (though it can be anything).
(I grew up on python, then C etc. at university, but as sibling says in Python I'd still `if __name__ == "__main__"`.)
You get this very nice linear execution birds' eye view which tends to be readable. Combine that with hoisting and you start with this big picture at the top of a file, and then can dig deeper into the smaller functions as needed, written in lexical order lower in the file.
Here is a very trivial JS example from a kata:
```js function main (numbers) { return Array .from(numbers) .sort(byGreatest) .slice(0, 2) .reduce(toSum) } ```
This is much more regularly written in languages with a pipeline operator, like Elixir, because you can pipe to arbitrary functions and operators (instead of being restricted to a method chain).
(JS/TS will get there eventually, if the TC39 committee can ever finally commit to the proposal.)