|
View previous topic :: View next topic
|
| Author |
Message |
casey Site Admin
Joined: 18 Dec 2004 Posts: 1768 Location: Seattle
|
| |
Posted: Sat Nov 14, 2009 4:51 am Post subject: |
 |
|
| Zaphos wrote: | | I see most of the things you are saying but what's the objection to manual importing of packages? |
Our whole notion of files and packages and all this sort of thing is just garbage today, in my opinion, and every new language seems to adopt the paradigm like it isn't a major problem that needs fixing.
In my opinion, the absolutely maximum I should have to do for a modern language is make sure all the relevant files are supplied on the command line when I invoke the build. That's it.
Why am I manually importing things? If I call a function, go find where the function is! Why am _I_ the one who has to go figure out where that function is?
And I can't avoid the obvious joke of a search company shipping a language where you have to manually find the functions yourself.
- Casey |
|
| Back to top |
|
 |
casey Site Admin
Joined: 18 Dec 2004 Posts: 1768 Location: Seattle
|
| |
Posted: Sat Nov 14, 2009 4:53 am Post subject: |
 |
|
| TomF wrote: | | You guys are killing me. That's not what assert is for. |
Yeah, ouch. It does seem to me that one of the prerequisites for writing a programming language these days is that you not be a production programmer. And I guess this is the sort of decision you end up with when that's the situation.
Asserts are a debugging tool. They have nothing to do with error handling.
- Casey |
|
| Back to top |
|
 |
ryg
Joined: 31 May 2007 Posts: 276 Location: Kirkland, WA
|
| |
Posted: Sat Nov 14, 2009 10:57 am Post subject: |
 |
|
| casey wrote: | Our whole notion of files and packages and all this sort of thing is just garbage today, in my opinion, and every new language seems to adopt the paradigm like it isn't a major problem that needs fixing.
In my opinion, the absolutely maximum I should have to do for a modern language is make sure all the relevant files are supplied on the command line when I invoke the build. That's it.
Why am I manually importing things? If I call a function, go find where the function is! Why am _I_ the one who has to go figure out where that function is? |
I could hardly disagree more on this particular point.
I agree that manually importing or including other modules is redundant in the sense that the compiler could figure it out by itself. But I don't think that this redundancy is in any way problematic, because there's someone else reading the source despite the compiler, namely programmers such as myself. I like having a bunch of includes/imports at the top of a source file, because that way I get to see explicitly who depends on what. If I want to change some module's interface for some reason (clean up something, split one function into two, etc.), I can grep for the header file name over the whole source tree and get a pretty good idea of who uses it, without having to read all the code. Most importantly, this works even for code that is currently commented/#ifdef'd out, not linked against, or otherwise excluded from build.
If you put this kind of functionality in the compiler or in other tools that need some sort of database that the compiler outputs, they're limited to only see what the compiler sees. That's bullshit - to give an example, I'm currently cleaning up and extending networking code for 4 platforms. That's four different compilers with four different SDKs and four very different implementations of mostly similar high-level interfaces. No matter which compiler I use, each only sees a fraction of the total codebase, so any sort of browse information output by the compiler is worse than useless for me - it's actively hiding information that may be very important for me.
On a more general note, I greatly prefer any system that explicitly states dependencies between components, even if it's technically redundant, because it makes the programmer aware of them. Having to add a few include directives every once in a while may be a nuisance, but being able to immediately spot files that depend on 30+ other modules (usually a surefire sign that they're in dire need of cleanup) is well worth it for me.
I still think the way this works in C/C++ is horribly broken; but the main problem is not having to state the dependencies manually. The two big ones for me are a) the textual include system, which is just grossly inefficient at large scales, and b) that you state your dependencies once to the compiler, and once to the linker, both in different ways, usually with different paths, with different name lookup and conflict resolution rules, and with absolutely no checks that the two are even talking about the same thing. (GNU ld gets a gold star on that last point, for default settings that are bound to screw you over in the most nonobvious ways possible).
| casey wrote: | | TomF wrote: | | You guys are killing me. That's not what assert is for. |
Yeah, ouch. It does seem to me that one of the prerequisites for writing a programming language these days is that you not be a production programmer. And I guess this is the sort of decision you end up with when that's the situation.
Asserts are a debugging tool. They have nothing to do with error handling. |
Yeah, I always assumed that was crystal clear to. Until about 3 months ago, when one of our main gameplay programmers and me had a lengthy discussion that started with "What do you mean by 'asserts are disabled in stripped builds'?". (We have them enabled by default in the daily builds as well as our internal tools; only the stripped versions we use for memory optimizations, final testing and mastering have all the debug stuff disabled).
Turned out we had a pretty clear divide in our team - the tech/engine guys, who all thought it was obvious that asserts were just a debugging tool and not to be used for any error checking that's supposed to be in the final product, and the gameplay guys, who were convinced that asserts were just supposed to be a quick way to write error handling.
"Oops". |
|
| Back to top |
|
 |
casey Site Admin
Joined: 18 Dec 2004 Posts: 1768 Location: Seattle
|
| |
Posted: Sat Nov 14, 2009 12:19 pm Post subject: |
 |
|
| ryg wrote: | | I like having a bunch of includes/imports at the top of a source file, because that way I get to see explicitly who depends on what. If I want to change some module's interface for some reason (clean up something, split one function into two, etc.), I can grep for the header file name over the whole source tree and get a pretty good idea of who uses it, without having to read all the code. Most importantly, this works even for code that is currently commented/#ifdef'd out, not linked against, or otherwise excluded from build. |
But manual import directives don't solve this problem at all. Maybe it seems like they solve it, because in some perfect word they would give you an idea of what depends on what, but in reality it's nonsense. I guarantee you that, much like in C/C++, you will end up with a bunch of "import" statements in various modules that are no longer necessary because the code that depended on them has been removed. Right?
So now you've just got this vestigial crap at the top of your files, basically, which is misleading.
Furthermore, the import directive doesn't tell you how something depends on something else. Does it use all the types from that module, or just one? Does it call any functions in there? Etc., etc.
What you want, for this sort of thing, is for the compiler not to be braindead like our current compilers are. It knows all the dependencies between modules, even in C++, because it had to do a lookup for every identifier and it found it somewhere. What you actually want is just a reporting option, so that when the compiler takes foo.cpp and makes foo.obj, it spits out foo.depends which lists where it found all the unresolved names.
This would allow you to actually do the kind of inspection that you wanted to do.
Furthermore, I would argue that you actually want this data per function, not per file, but that gets back to my whole "files are stupid" thing.
- Casey |
|
| Back to top |
|
 |
ryg
Joined: 31 May 2007 Posts: 276 Location: Kirkland, WA
|
| |
Posted: Sat Nov 14, 2009 1:18 pm Post subject: |
 |
|
| casey wrote: | | But manual import directives don't solve this problem at all. Maybe it seems like they solve it, because in some perfect word they would give you an idea of what depends on what, but in reality it's nonsense. I guarantee you that, much like in C/C++, you will end up with a bunch of "import" statements in various modules that are no longer necessary because the code that depended on them has been removed. Right? |
Go does solve that particular problem, since having an import directive for a module that you don't end up referencing actually is a compile-time error. Quoting from the spec:
| Quote: | | It is illegal for a package to import itself or to import a package without referring to any of its exported identifiers. |
So you have the explicit declarations, with the advantage of being explicit and the disadvantage/nuisance of being technically redundant, but with the guarantee that this dependency information is accurate - as long as we're talking about files that are compiled regularly. Which is a good compromise as far as I'm concerned.
| casey wrote: | | So now you've just got this vestigial crap at the top of your files, basically, which is misleading. |
I agree that it's annoying. But I still think that partial dependency information is worthwhile, as long as it errs on the "too pessimistic" side. As mentioned above, the killer application for this is the "grep for this header file name" that I mentioned above, which is something I do a lot. And when I do interface changes or cleanups, I need to touch a lot of files that use a certain module anyway - removing unnecessary includes to the referenced module is a rather natural part of that workflow for me ("as long as I'm going through a list of files anyway..."), which mitigates the problem somewhat. For the files I work with at least. Others certainly have a different style, and I certainly do count it as a significant defect that one or two programmers with a "start adding includes everywhere until it compiles" mentality can absolutely wreck build times for everyone else.
| casey wrote: | | Furthermore, the import directive doesn't tell you how something depends on something else. Does it use all the types from that module, or just one? Does it call any functions in there? Etc., etc. |
That's true, and I don't think it's a problem. As said, I think that the main beneficiary for manual include directives are programmers, not the compiler. The main question then is what granularity to work on. Too coarse and the information is useless to your mental model of the program. For example, "which libraries do I link against" is too coarse-grained to aid in understanding a problem. (Of course the linker doesn't mind, since it's a search-based architecture internally). On the other end, which functions specifically are imported is usually more detail than I need (and besides something I can find out quickly using dumpbin/objdump if I actually care).
This is obviously very dependent on project organization; but in the codebases I've worked on so far, the amount of functionality declared in one package/header file has always been within my "comfort zone", so the granularity suits me. It certainly breaks down when you have very rigid rules like "only one class per file", since that's too detailed for me to get the big picture. (Besides, it's definitely past the point where maintaining #includes turns from a nuisance into an annoyance).
| casey wrote: | What you want, for this sort of thing, is for the compiler not to be braindead like our current compilers are. It knows all the dependencies between modules, even in C++, because it had to do a lookup for every identifier and it found it somewhere. What you actually want is just a reporting option, so that when the compiler takes foo.cpp and makes foo.obj, it spits out foo.depends which lists where it found all the unresolved names.
This would allow you to actually do the kind of inspection that you wanted to do. |
It's still file-level though, which somewhat conflicts with what you said above. I certainly agree that this would be the right thing to do though.
| casey wrote: | | Furthermore, I would argue that you actually want this data per function, not per file, but that gets back to my whole "files are stupid" thing. |
I agree that I would want this data to be available, but that's not saying much. I'm a data freak. I always want as much hard data as possible - that's why I write things like the code-size analysis stuff and so on.
But that general point aside, there's still a very important tradeoff here. If there's too little data, you gain no insight. If there's too much, you end up having to use data mining techniques and lots of aggregation to extract the information you need out of it. After all, that's what the kkrunchy pack ratio analysis and Sizer do: take a complete PDB (or MAP file) and compute one single metric per function/data item, out of a multi-megabyte description of all kinds of aspects of the compiled program. Both extremes end up with you having to write specific tools to get the information you want, even though it's for different reasons.
Yes, it would be nice to have a compiler that does a way better job of providing not just information about the compiled code, but also its relation to the original source code. And yes, that type of innovation in compilers is happening at an awfully slow pace. (It is happening, though).
But there's still lots to be said for optimizing the "average case" by putting some key information that you're gonna want to look at often right there in the file and make it more self-describing. To me that's about the same level as a subject line in email. It might be outright misleading and it won't add anything new, but it's still useful to have. |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Sun Nov 15, 2009 1:06 am Post subject: |
 |
|
In light of Go I took another look at issues for my language Stu. Given the purpose of the language (which I explain in the first bullet), it may be of limited interest.
http://stu-lang.livejournal.com/5200.html |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Sun Nov 15, 2009 9:50 pm Post subject: |
 |
|
| Another, different kind of critique of Go. |
|
| Back to top |
|
 |
TomF
Joined: 18 Feb 2007 Posts: 107 Location: Seattle
|
| |
Posted: Sun Nov 15, 2009 11:42 pm Post subject: |
 |
|
| Ugh - I hadn't noticed the almost complete lack of immutables (const references, references to const, etc) in Go. That's going to get annoying very quickly, especially for functional and/or parallel computing. |
|
| Back to top |
|
 |
Zaphos
Joined: 24 Feb 2007 Posts: 90
|
| |
Posted: Wed Nov 18, 2009 2:03 am Post subject: |
 |
|
| sean wrote: |
Have you ever actually touched somebody else's code that was authored using a different tab setting from your own, and uses tabs?
|
Heh, I have actually, and it made me super annoyed at the time, but I think I forgot some of the frustrations -- it was some time ago. It was especially obnoxious too because they used tabs for compression, so it had some awkward mix of tabs and spaces ...
So yeah, fair points, I see it can get pretty bad, especially with internal spacing.
Also, I tested out gofmt and it seems it just nukes internal spacing entirely. Hmmmm. |
|
| Back to top |
|
 |
ryg
Joined: 31 May 2007 Posts: 276 Location: Kirkland, WA
|
| |
Posted: Sat Feb 06, 2010 12:16 pm Post subject: |
 |
|
I just noticed this while reading the "Go For C++ Programmers" page (the first time I just skimmed it), and I think it takes the cake:
| Go For C++ Programmers wrote: | Go code uses very few semicolons in practice. Technically, all Go statements are terminated by a semicolon. However, Go treats the end of a non-blank line as a semicolon unless the line is clearly incomplete (the exact rules are in the language specification). A consequence of this is that in some cases Go does not permit you to use a line break. For example, you may not write
| Code: | func g()
{ // INVALID
} |
A semicolon will be inserted after g(), causing it to be a function declaration rather than a function definition. Similarly, you may not write
| Code: | if x {
}
else { // INVALID
} |
A semicolon will be inserted after the } preceding the else, causing a syntax error. |
Okay, so you've got semicolons in the grammar but not most source files, since your lexer automatically inserts them most of the time unless your line is blank or meets some rather arbitrary rules listed in the spec (and evidently considered to be too long to recite in full). That's just a bad idea on so many levels. And it's even worse than it first appears, if that's even possible! It happens to enforce K&R-style C identing - which seems weird and unnecessary, but whatever - but it also means that this:
| Code: | a = b + c + d + e +
f + g |
is valid while this:
| Code: | a = b + c + d + e
+ f + g |
produces a syntax error (on the second line!). Even worse, if you use minus signs instead:
| Code: | a = b - c - d - e
- f - g |
this is silently parsed as "a = b - c - d - e; -f - g;", probably not what was intended!
It really seems like they charted all the possible ways to get the syntactic appearance they wanted and then decided to go for the worst possible implementation. I think that assigning whitespace a syntactic function is a bad idea in general, mainly because of the tab/space situation that ensures that "these statements line up to me" doesn't necessarily mean that "these statements line up to the compiler". But not even giving newlines any function in the grammer and instead substituting them with semicolons in the lexer, that's just... eugh. |
|
| Back to top |
|
 |
sean
Joined: 01 Feb 2005 Posts: 1392 Location: Kirkland WA
|
| |
Posted: Sat Feb 06, 2010 3:12 pm Post subject: |
 |
|
I suspect those optional semicolon rules are just copied from javascript.
Or are they subtly different? |
|
| Back to top |
|
 |
ryg
Joined: 31 May 2007 Posts: 276 Location: Kirkland, WA
|
| |
Posted: Sat Feb 06, 2010 5:25 pm Post subject: |
 |
|
| sean wrote: | I suspect those optional semicolon rules are just copied from javascript.
Or are they subtly different? |
Oh, didn't know that JavaScript did this too. I haven't managed to find a clear description of the semicolon insertion rules in JavaScript - it seems to be that JavaScript will automatically insert a semicolon at a newline whenever not doing so would result in a syntax error, but I'm not certain, and nobody really seems to be sure (I guess it's in the standard somewhere).
Anyway, they seem to be different. One example given here for JavaScript:
| Code: | a = b + c
(d + e).print() |
which JS parses as
| Code: | | a = b + c(d + e).print(); |
would parse as expected in Go.
Go at least makes a point of defining what exactly a probably incomplete line looks like, but I still think the most sensible (least confusing) solution if you want newlines to terminate statements is to just always do it unless the user specifically requests the line to be continued (like \ in the C preprocessor). |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|