On Syntax Flexibility

posted on

It's no secret that I'm not a big fan of Ruby's syntax. I've grown to love Ruby's functionality and I would like to see language support for some of it in Objective-C (symbols, non-alphanumeric method names and modules/mixins come to mind). I'm also not too opposed to the syntax of Ruby per say. It is concise and fairly readable.

The issue I have is the huge amounts of flexibility there is in the syntax. A bit of flexibility is nice, especially for conciseness. I wouldn't mind the ability to define arrays, dictionaries, sets and numbers in a shorter syntax in Objective-C. The issue I have is where flexibility causes ambiguity, especially when something is completely identical. This is why I have an issue with the dot syntax in Objective-C, as it looks identical to struct access and adds ambiguity.

Examples

I had a discussion with someone over IM about this and I gave an example. Take this line of ruby:

validates :title, :uniqueness => true

Harmless enough. But it is completely identical to these 5 as well:

validates(:title, uniqueness: true)
validates(:title, :uniqueness => true)
validates(:title, {:uniqueness => true})
validates :title, uniqueness: true
validates :title, {:uniqueness => true}

Now the person I was talking to fired back with an example from C/Objective-C, showing 5 ways to write the same statement:

if (blah == 3) {
	return NO;
} else {
	return YES;
}
if (blah == 3)
	return NO;
else
	return YES;
if (blah == 3)
{
	return NO;
}
else
{
	return YES;
}
if (blah == 3) {
	return NO;
}
else {
	return YES;
}
return (blah == 3 ? NO : YES);

He also missed the basic "return blah == 3". Now at first glance he has a point, 6 ways including the one he missed to write the same thing. There are some issues with the argument though. While they are all logically identical they aren't functionally identical. To give an example of the difference, the following two statements are logically identical but functionally different:

for (NSUInteger i = 0; i < 5; i++) {
	NSLog(@"%d", i);
}
NSUInteger i = 0;
while (i < 5) {
	NSLog(@"%d", i);
	i++;
}

Logically, they are both the same, they are looping through until i reaches 5, printing out the value of i and then incrementing it each time. However, one is a for loop and one is a while loop, the are completely different constructs. There is also no ambiguity as to which one is which.

Now 3 of those 6 Objective-C examples are not only logically and functionally identical, but minus whitespace they are completely identical, so we can throw 2 of those away. The two single line ones are functionally different as well, though they are logically identical. This means we have two logically and functionally identical items with different syntax:

if (blah == 3) {
	return NO;
} else {
	return YES;
}
if (blah == 3)
	return NO;
else
	return YES;

Now, I'm sometimes guilty of leaving out curly brackets on one line statements myself, but the issue is that it can cause ambiguity and therefore bugs. I could add a second statement and it would be fine in the first version but could break in the second unless I remember to add the brackets in. While it may annoy some users, it wouldn't affect me in the slightest if curly brackets were required in all cases and you got a compiler error if you miss them out.

Ruby's Ambiguity

validates :title, :uniqueness => true

The issue with the above statement is that it doesn't fully state what is what. It is a list of words. Some people may feel differently but to me it is trading a little bit of conciseness in order for ambiguity. Sure, a seasoned ruby expert could tell you that it is a method with two arguments, the first of which is a symbol, the second of which is a hash. But the issue is that the code doesn't explicitly say that. It gets even worse if you have multiple items in a dictionary:

method :symbol, :key1 => true, :key2 => "foo"

At first this looks like a method that takes 3 arguments. In fact it is one that takes two arguments. Written in an unambiguous way it would be:

method(:symbol, {:key1 => true, :key2 => "foo"})

Convention Over Configuration

What prompted this post is the response I got to a tweet where I said that the fact that Ruby has so much optional syntax makes it feel like an app with a massive prefs window. Ideally as a software designer, you should be aiming to use sensible defaults rather than adding a preference. Adding a preference should feel like a cop out. The ideal number of preferences in any app should be 0.

Ruby feels like it goes in the opposite direction at times. It adds a preference because it might be nice for some people to do it that way. It seems ironic that it then has a framework which is built upon the core principle of convention over configuration.

Now yes, some people will say "well don't use Ruby then" or "just use the syntax you want". But the thing is that Ruby is a very good language under the hood. It is incredibly similar to my favourite language: Objective-C. So much so that it can run on top of Objective-C's runtime. Rails is also a very good API and has some very smart people behind it so it is worth using. While some people I know dislike Objective-C, they put up with it because they enjoy Cocoa and believe it to be a very good API. This is no different with me and Rails.

It all comes down to the syntax and the fact that many people use different forms of it. I prefer to code explicitly, so that the meaning of the code is absolutely clear and if this means being a bit more verbose then so be it. Others don't like to be that way, and while I don't aim to change their mind I do feel I need to have a little whinge about it so I can explain my position and why I'm whinging in the first place.