Thursday, January 10, 2013

Don't Use Objective-C

This isn't OS X or iOS, it's Linux. You can program in whatever you damn well please. To that end, let's try something simple. Let's make a Point class and extend it to a Euclidean Vector. At the onset, let's assume we're in a hurry and want to write short code.

Here it is in C++:
class Point {
public:
        double x, y, z;
        Point(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}
        virtual string toString() {
                stringstream ss;
                ss << "(" << x << ", " << y << ", " << z << ")";
                return ss.str();
        }
};
class Vector : public Point {
public:
        Vector(double x = 0, double y = 0, double z = 0) : Point(x, y, z) {}
        virtual string toString() {
                stringstream ss;
                ss << "<" << x << ", " << y << ", " << z << ">";
                return ss.str();
        }
        Vector add(const Vector& other) const { return Vector(x + other.x, y + other.y, z + other.z); }
        inline Vector operator+(const Vector& other) const { return add(other); }
};

int main()
{
        Point p1(1,2,3), p2;
        cout << p1.toString() << endl;
        cout << p2.toString() << endl;
        Vector v1(1,2,3), v2(2,3,4);
        cout << v1.toString() << endl;
        cout << v2.toString() << endl;
        cout << (v1+v2).toString() << endl;
}

Pretty standard stuff. Here it is in Java:
class Test
{
        static class Point {
                public double x, y, z;
                Point(double x, double y, double z) {
                        this.x = x; this.y = y; this.z = z;
                }
                Point(double x, double y) { this(x, y, 0); }
                Point(double x) { this(x, 0, 0); }
                Point() { this(0, 0, 0); }
                @Override
                public String toString() { return "(" + x + ", " + y + ", " + z + ")"; }
        }
        static class Vector extends Point {
                Vector(double x, double y, double z) { super(x, y, z); }
                Vector(double x, double y) { super(x, y, 0); }
                Vector(double x) { super(x, 0, 0); }
                Vector() { super(0, 0, 0); }
                @Override
                public String toString() { return "<" + x + ", " + y + ", " + z + ">"; }
                public Vector add(Vector other) { return new Vector(x + other.x, y + other.y, z + other.z); }
        }
        public static void main(String[] args) {
                Point p1 = new Point(1, 2, 3), p2 = new Point();
                System.out.println(p1);
                System.out.println(p2);
                Vector v1 = new Vector(1, 2, 3), v2 = new Vector(2, 3, 4);
                System.out.println(v1);
                System.out.println(v2);
                System.out.println(v1.add(v2));
        }
}
No default arguments and no operator overloading, but I can get over that. I'd show a C# one or something, but it's like Java with operator overloading and default arguments, so there isn't much point. But, hey, I don't really use VB, but I'll throw in some VB.Net so we have something with some significantly different syntax:
Module Test
        Class Point
                Public x, y, z As Double
                Public Sub New(Optional x as Double = 0, Optional y as Double = 0, Optional z as Double = 0)
                        Me.x = x
                        Me.y = y
                        Me.z = z
                End Sub
                Public Overridable Function ToString As String
                        Return "(" & x & ", " & y & ", " & z & ")"
                End Function
        End Class
        Class Vector
                Inherits Point
                Public Sub New(Optional x as Double = 0, Optional y as Double = 0, Optional z as Double = 0)
                        MyBase.New(x, y, z)
                End Sub
                Public Overridable Function ToString As String
                        Return "<" & x & ", " & y & ", " & z & ">"
                End Function
                Public Function Add(other as Vector) As Vector
                        Return New Vector(x + other.x, y + other.y, z + other.z)
                End Function
        End Class
        Sub Main()
                Dim p1 As Point = new Point(1, 2, 3)
                Dim p2 As Point = new Point()
                Console.WriteLine(p1.ToString())
                Console.WriteLine(p2.ToString())
                Dim v1 As Vector = new Vector(1, 2, 3)
                Dim v2 As Vector = new Vector()
                Console.WriteLine(v1.ToString())
                Console.WriteLine(v2.ToString())
                Console.WriteLine(v1.Add(v2).ToString())
        End Sub
End Module 
I absolutely could not figure out the operator overloading syntax, but I'm assured that it's possible. The code looks uglier to me, but it's pretty readable. Now let's check out Objective-C:

@interface Point : NSObject
{
        @public double x, y, z;
}
- (id) init;
- (id) init:(double)x;
- (id) init:(double)x :(double)y;
- (id) init:(double)x :(double)y :(double)z;

- (NSString *) description;
@end
@implementation Point
- (id) init
{
        self = [super init];
        if (self)
        {
                // no need because alloc zeros everything out
        }
        else // something went wrong with super init
        {
                NSLog(@"Superclass failed to init.\n");
        }
        return self;
}
- (id) init:(double)x { return [self init:x :0 :0]; }
- (id) init:(double)x :(double)y { return [self init:x :y :0]; }
- (id) init:(double)x :(double)y :(double)z
{
        self = [self init];
        self->x = x;
        self->y = y;
        self->z = z;
        return self;
}
- (NSString *) description
{
        return [NSString stringWithFormat: @"(%g, %g, %g)", self->x, self->y, self->z];
}
@end

@interface Vector : Point
- (Vector *) add:(Vector *)other;
- (NSString *) description;
@end
@implementation Vector
- (NSString *) description
{
        return [NSString stringWithFormat: @"<%g, %g, %g>", self->x, self->y, self->z];
}
- (Vector *) add:(Vector *)other
{
        return [[Vector alloc] init:x + other->x :y + other->y :z + other->z];
}
@end

int main (void)
{
        NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

        Point * p1 = [[Point alloc] init:1 :2 :3], * p2 = [Point new];
        GSPrintf(stdout, @"%@\n", p1);
        GSPrintf(stdout, @"%@\n", p2);

        Vector * v1 = [[Vector alloc] init:1 :2 :3], * v2 = [[Vector alloc] init:2 :3 :4];
        GSPrintf(stdout, @"%@\n", v1);
        GSPrintf(stdout, @"%@\n", v2);
        GSPrintf(stdout, @"%@\n", [v1 add:v2]);

        [pool drain];
        return(0);
}

What the fuck is this? "-" to indicate a nonstatic member? What's with the ridiculous "[]" syntax? What's with the ridiculous syntax in general? Okay, whatever. There's no constructor really, but the the "init" thing is more or less the same and I can override it so that's kind of cool. Also, I kind of like the forced separation between interface and implementation (I am supposed to put one in a header file). The ugly code is hardly the worst of it.

There's also no operator overloading. Now, in Java, that means:
v1.add(v2).add(v3).add(v4)...
which isn't all that bad (actually, it's horrible if you have some complex operations planned). In objective-C, this means
[[[[v1 add:v2] add:v3] add:v4] ... ]
which is horrifying enough as it is and even more so with something fancier.

Let's say I want to choose the scalar datatype (ints or floats instead of doubles). In C++, Java and just about anything that isn't retarded, I can use templates/generics. With objective-C, I can... I don't know, try to get creative with the C preprocessor, maybe.

Let's say I want arbitrary dimensions. That means an array of data. I can use C-style arrays, so that's cool. Let's say I want to use something fancier, though, like an STL vector or an ArrayList. My Point/Vector example is pretty bad for this, but there are many situations where you want something you can resize. In objective-C, you get an NSMutableArray. This is what the code looks like:

NSMutableArray *myIntegers = [NSMutableArray array];

for (NSInteger i = 0; i < 40; i++)
    [myIntegers addObject:[NSNumber numberWithInteger:i]];
  
Notice anything? The fact that every fucking number has to be wrapped in an object, perhaps. Each of these objects, at the very least, carries with it a pointer overhead, at least doubling the memory requirements for each element (8-byte pointers to 4- or 8-byte data, except that NSNumber is probably way bigger than that), not to mention the pointer dereferencing overhead.

Now, let's factor in the fact that in C++, you can choose whether or not to dynamically allocate objects. In objective-C, all objects absolutely have to be dynamically allocated, much like in Java/C#/VB and many others (except you don't get the benefits of Java like reasonable syntax). Now, there are very good reasons to dynamically allocate things, but for performance reasons, I don't want to always do that. As a side note, Atmel microcontrollers (like the one on the popular Arduino board) let you program them in C++ (and whatever else you want for the ARM-based ones, at least), but there's no equivalent to malloc() due to the limited memory. Having a language as low-level as objective-C that lacks many of the benefits of higher-level languages, I at least expect some performance benefits, like being able to use static memory allocation.

There's also some weird stuff, like things that should clearly be errors being reported as warnings by the compiler. The fact that I managed to instantiate a Vector as Point (not the other way around) was also weird. The "@private" keyword seems to be more of a polite suggestion than any sort of limitation.

Methods can also be declared as:
- (type)name:(type)x more:(type)y evenmore:(type)z
You'd think that means you can do something useful with those extra labels. In fact, it just means the function name is "name:more:evenmore". When calling it, you have to get the damn thing exactly right, i.e.
[object name:x more:y evenmore:z]
It's questionable why that's better than
- (type)nameMoreEvenmore:(type)x :(type)y :(type)z
in any significant way.

Let's get even more nitpicky. Using printf() is possible, but with most languages, you get something nicer, like cout/System.out/Console. With the normal (foundation) library (the one with all the NS* functions, which probably has a better name), you get NSLog, which prepends timestamps and stuff. That's kind of annoying. The best thing you can get is GSPrintf(), which works quite well, but I'm pretty sure that's only available with GNUStep (because the function name starts with GS). What do the OS X developers do? Have none of them ever wanted to just print something real fast?

I'm not really sure what the deal with this language is, and I absolutely don't understand why Apple likes it so much. The syntax is nuts and seems to lack a lot of useful features. To finish off, I'll list a few languages that you'd probably be better off using (in no particular order):
  • pure C89/C99 (at least it has nice syntax)
  • C++
  • Java
  • C#
  • D (this one lacks decent libraries, but has really nice syntax)
  • VB.Net (if you're really desperate and/or bored)
  • Python
  • Perl
  • Fortran, if you know it (I don't because I was a 1-year-old when the Wall fell and Fortran was at its peak)
I hope I didn't piss anyone off too much. If someone developing the language happens to read this, implement encapsulation properly, add support for generics and operator overloading, and I might consider your language moderately useful.

No comments:

Post a Comment