mag wrote:
I don't think there's anything fundamental to imperative languages that makes them have to have substantially more syntax.
Perhaps not substantially more syntax, it is pretty hard to quantify this type of thing. As we discussed before, in an imperative programming language (IPL), you need to know the operators, which is a wash because you need to know the functions in a functional programming language (FPL), but the IPL also requires you to be familiar with the operator precedence rules. The real difference is that the grammar in an IPL is almost always larger. For example, why doesn't the following code compile?
Code:
int bar = if (foo) { 1 } else { 2 }; //You have to be familiar with the ternary operator... int bar = (foo) ? 1 : 2;
CL-USER> (setf bar (if foo 1 2)) #Just use the basic syntax... you don't need to know another operator
1
CL-USER> bar #foo had been set to true...
1
This doesn't compile in Java, C/C++ or any other IPL (AFAIK), but using the if function in an assignment function is perfectly natural in a FPL. I can only use an if condition if it "starts a line", or more explicitly, whenever the grammar says it is OK to use an if condition. I also have to watch out for dangling if clauses and things of that nature (which aren't an issue with an FPL due to the order of function calls being explicitly defined by the user). This may seem like a fairly trivial example, but there have been plenty of occasions when I've been writing code in Java and thought "Lisp sure would have made that easier".
In most IPLs, elements of an array, list and string are accessed using different syntax. In Java, I access the first element of an array with foo[0], but the first element of a list is accessed using foo.get(0), and the first element of a string using charAt(), which returns a char instead of a string with a single element (This requires a substring() call). To find the length of an array, I access the public length member (ie foo.length). The length of a string requires a method call to length() while the list a call to size(). Accessing part of a list, array and string also require different methods (esp for an array). And frankly, Java is much easier to remember than C or C++ (especially when dealing with strings). In CL, these data types are all accessed using the same function calls w/o any variation in syntax.
Code:
CL-USER> (setf foo (list 0 1 2 3 4)
bar (make-array (length foo) :initial-contents foo)
baz "01234")
"01234"
CL-USER> (elt foo 0)
0
CL-USER> (elt bar 0)
0
CL-USER> (elt baz 0)
#\0
CL-USER> (length foo)
5
CL-USER> (length bar)
5
CL-USER> (length baz)
#\3
CL-USER> (subseq baz 0 3)
"012"
CL-USER> (subseq bar 0 3)
#(0 1 2)
CL-USER> (subseq foo 0 3)
(0 1 2)
Many IPLs utilize pointers which add another layer of syntax for how to allocate and free memory. Again, there is syntatic sugar for pointer assignments in C/C++. More syntax to memorize.
Typically, IPLs have a number of reserved and/or keywords. I believe Java has 34 keywords and a few reserved words. There are also usually a number of grammar rules concerning naming local variables, global variables and functions. Often this is due to how operators and variables or functions interact. In most IPLs, I can't name a variable a+b even though that may be a perfect description for the variable!
Code:
CL-USER> (setf 1a 1
b-2 2
1a+b-2 (+ 1a b-2))
3
CL-USER> (format t "~d ~d ~d~%" 1a b-2 1a+b-2)
1 2 3
In C and C++, there is some interesting syntax related to global variables and variable scoping. The following code will probably test your memory pretty well. C++ also has the oh-so-fun macro system.
Code:
#include<iostream> //right? Or is it supposed to be...
#include"iostream" //???
int x;
void f() {
int x = 1;
x = 2; //which x was just updated?
::x = 3; //and now?
{
int x = 4;
x++;
::x++;
}
}
Another interesting area to contrast is function declarations. This probably the one place where the FPLs have more syntax than the IPLs, because they generally support more language features related to functions (eg parameter declaration, multiple return values, higher order functions, returning functions, etc), but the IPLs still manage to make things more difficult IMO. Take these C++ examples using default parameter values and multiple arguments.
Code:
int f(int x, int y=2, int z = 3); //This is ok
int g(int x=1, int y=2, int z); //This is an error
int h(int ...); //We don't know how many integers will be passed.
For some reason, all parameters without default values must come before parameters with default values. Why? Who knows... just put it in the memory banks. Nope, you don't have to worry about that type of thing in CL. Occasionally you have to write a function that may take any number of arguments (eg the mean() and sum() functions in R). Fine, I just use an ellipsis, right? Well, you need to be able to access those args in the function. How? The <cstdarg> macro to the rescue. That macro is more fun than a migraine... let me tell you.
Several IPLs support operator overloading, which can be convenient, but you must learn additional syntax/grammar rules. Do you know which of the 42 operators can be overloaded in C++. Do you know the three operators that cannot be overloaded? Did you know there were 45 operators?!
In the case of C and C++, you also have header files and the macro system to contend with as well.
mag wrote:
I'd agree, I'd say the prefix and postfix operators are syntactic sugar.
Sure, they are syntactic sugar. However, languages include syntactic sugar because it is something that is done frequently. If anything, you should probably especially familiar with the syntactic sugar in a language because you're going to see it being used all the time. If you actually wrote "int n = n + 1;", you're boss is going to think something is wrong with you! =)