I considered many candidates for function declaration syntax. Imagine we want to declare a function named ‘foo’ that returns int and has three parameters, x, y, and z. Here are some of the candidates I considered:
func foo:int x:int y:short z:char
func foo:int params x:int y:short z:char
func foo returntype int params x:int y:short z:char
func foo rtype int params x:int y:short z:char
func foo int params x:int y:short z:char
func foo int x:int y:short z:char
func foo x:int y:short z:char rtype int
func foo x:int y:short z:char returntype int
[hey, just found out you can preserve coloring and basic formatting when copying from OpenOffice into the WordPress text box. Neat trick.]
My current thinking is to use the last form, for a few reasons:
- Return types are a new concept, as are statically typed parameters. While experienced C/Java programmers should have no trouble thinking of the return type as the type of the function and hence understand the foo:int syntax, I ultimately concluded it’s important to keep the concepts of return type and parameter type syntactically distinct.
- I considered abbreviating Pygeon’s ‘parameters’ keyword to ‘params’, but because it’s assumed students have learned Pygeon before attempting PygeonC, it makes sense to just get rid of it at this point, as it’s unnecessary extra typing. Besides, I don’t like how it makes functions with parameters look too different from functions without; such non-uniformity makes it harder to identify functions when quickly scrolling past them.
- On the other hand, return type, being a new concept, could stand to stand out, hence announcing it with a keyword and placing it at the end.
- I’m thinking that ‘returntype’ is too much to type, but it may still be the best option. I considered ‘returns’, but that’s confusingly close to ‘return’, and I always find imitating English sentences in syntax to be misguided, not a virtue. Abbreviating to ‘rt’ is too cryptic. My concern with ‘rtype’ is that “ARR-TYPE” might enter into the learner’s vocabulary; I don’t like the idea of polluting the already confused lexicon of programming.
- I’ve argued elsewhere that superfluous syntax is usually just confusing to learners, which suggests ‘returntype’ should be omitted, leaving the return type to just be inferred as the last type given. Like with having the ‘parameters’ keyword in Pygeon, though, ‘returntype’ may just help hammer the vocabulary term into learners’ heads. As long as it’s pointed out to students that the syntax could easily ditch ‘returntype’, I think this is a fair trade off.
(Note the type keywords are highlighted in a different color than the normal blue color. In fact, I’m thinking all type specifications should be highlighted uniformly whether they are built-in type keywords or not.)
Whatever the chosen function definition syntax, the prototype syntax is basically the same but with ‘proto’ in place of ‘func’ and with just parameter types, not names:
proto foo int short char returntype int
In fact, PygeonC doesn’t let you specify the parameter names in a prototype, for having optional syntax that doesn’t even do anything is frankly just confusing, and I consider it a core concept of C prototypes the fact that the compiler disregards any parameter names present. Besides, the utility of including parameter names in C prototypes is having self-documented code, something which is not really a concern for learners at this stage.
You might argue that prototypes could simply start with ‘func’, just like functions, but I think this is precisely the mistake C makes. Sure, function declaration and function definition are conceptually related, but giving them only subtly distinct syntaxes, as C does, makes them initially hard to discern and blurs the conceptual differences in the minds of learners. Generally speaking, subtlety is not a virtue for making things grokable.
The definition/declaration blur is another example of a misguided attempt at conceptual unity in C. I think what’s going on with such misguided conceptual unities is that designers spend their time juggling many parts in their head, mentally banging them together to see which parts fit with which and which overlay the others; the best part of designing comes when you have those ‘A ha!’ moments, where you see how parts you were thinking of as separate can be neatly overlayed, interlocked, or even dissolved into one, greatly simplifying the design. A small minority of the time, these revelatory moments really pan out just like they seem they will in that initial flash of recognition. However, most of these moments come to nothing when it later turns out, upon further reflection, that the idea doesn’t really make sense or fit consistently with the rest of the design. Other times, refactoring the rest of the design to fit the revelatory idea actually makes the whole more complicated. Other times, the idea can be accommodated just fine, but the gain is just an illusion: the designer, unhappy with some trade off, finds a solution that seems to dissolve that trade off, but euphoria blinds the designer to some side-effect introduced by his solution; on any other day, this new problem would displease the designer just as much as the problem he’s just solved, but he just isn’t thinking about it at the moment—maybe he’ll notice in a week or two.
The aspects of C that displease me so, the misguided attempts at conceptual unity, I actually believe these are partly examples of success: after all, the conceptual unities of C ‘work’ in the sense that (obviously) they comprise a real working language and the conceptual unities do in fact reduce the syntax (e.g. pointer and array declaration syntax mirrors the syntax for dereferencing and array indexing). However, these design choices also exhibit the designer ‘euphoria blindness’ I described: aspects of the design have been simplified, but only by incurring disregarded costs elsewhere. In this case, the costs are to language transparency. The design of a language is a kind of design where the transparency of the design is almost as important as its functionality, but the misguided conceptual unities in C are difficult to convey to outsiders because they really only make sense to people who already understand them. Having read many accounts of the C language, I’ve come to the conclusion that many of the traditional stories and vocabulary which C programmers use to talk about the language to each other simply fail to account for what is really going on in the language. Really, this is an unfortunate fact of any area of expertise: the experts are already cognizant of what’s really going on, so it’s fine for communication amongst themselves if their explanations and vocabulary abridge or misrepresent to untrained ears what they’re actually saying.
Anyway, on to function pointers. Let’s say we wish to declare a pointer named ‘bar’ for the function ‘foo’, above. Here’s the syntax I currently have in mind:
bar:[pfunc int short char returntype int]
(Notice the whole type declaration is in a single-color highlighting. Again, ‘pfunc’ and ‘returntype’ could be omitted, leaving the return type to be inferred as the last type specified in the brackets, but I think it’s best to leave those training wheels on.)
Any occurrence of [] always denotes a function pointer type. You can create derived types from function pointers using the usual scheme, e.g. if you wanted a 3-element array of pointers to a function pointer, it would look like so:
bar:3-p-[pfunc int short char returntype int]
Function pointer syntax requires start and end delimiters of course because you might need a pointer to a function that has a function pointer parameter:
bar:[pfunc int [pfunc char returntype p-char] returntype int]
(Above, ‘bar’ is a pointer to a function that takes a function pointer as its second argument.)
I’ve yet to read an introductory C book other than K&R that even mentions function pointers, and I think for good reason considering the crazy way C denotes them. Hopefully the above syntax can correct this.
UPDATE: Upon further consideration, ‘rt’ is the right choice. Also, ‘pfunc’ should just be omitted, as it’s enough to say that every pair of brackets is a function pointer signature. So now function definitions, prototypes, and function pointer declarations should look like, respectively:
func foo x:int y:short z:char rt int
proto foo int short char rt int
bar:[int short char rt int]
Another issue is casting. My first notion was to reuse the declaration syntax, like so:
foo:p-void // cast foo to pointer-to-a-void
…but then I realized this looks odd when applied to a parenthetical expression:
(bar a b):p-void
I also dislike how it gave colons two subtley related purposes. If casting is an operation, best treat it consistently with other operations and have a casting operator:
(cast p-void foo) // cast foo to pointer-to-a-void
[...] Here’s some nice paragraphs recycled from an old crappy post no longer worth reading. [...]