Thoughts of a C/C++ programmer after years in JavaScript

2015-04-23

If you haven't read the 4 stages of a C/C++ programmer using JavaScript this is a follow up on that so you might want to read that first.

Not that anyone reads this website I only update 3 times a year now but before you get your stockings in a bunch I'm not saying JavaScript rocks. I'm only saying that after using it for many years there's things I've come to love that I now can't stand when I'm not in it. Example include

(*) no setup

Every device has a browser.

(*) useful apis that just work across platforms

including canvas, webgl, audio api, camera access, gps, etc

Think about what I've have to do in C++ to get a cross platform canvas2d like API. I'd probably have to build Skia which is like a gazillion lines of code. I'd have to figure out what bazillion options to pass it etc, watch it compile for 10−15−20 mins and hope no obscure errors pop out.

Or let's take WebGL, sure you've got OpenGL on desktops. But, if you actually do any OpenGL programming you'll find that all the drivers are fucked up in some way you're not aware of. Then you have to deal with issues of ES (mobile) vs non−ES (desktop). You also have Windows where if your users are not nerds/gamers it's unlikely they have OpenGL drivers installed. The browsers have solved all of this for you. For the most part WebGL just works, they've worked around the bugs for you. Same with audio, and the rest.

Sure you can point to something like SDL but does it run on Android, iOS? I'm sure you've got a camera library in libcinder or openframeworks but again does it run on some mobile or only OSX/Windows/Linux?

(*) no build generally so no platform specific IDEs or build systems

If I've got a Visual Studio project it doesn't work on Linux or OSX. If I've got a makefile it's unlikely to work on Windows. Sure if you get a super complicated program you might setup some pre−compilation step. This is pretty much cross platform at this point through node/grunt/gulp etc..

(*) Sharing is so easy

No downloading, no worrying about viruses or having to scan something, no corp firewall issues likely, no needing one of each target platform to build on.

(*) JavaScript's loose typing makes making shit often ridiculously simple once you stop trying to bring C++/Java/C# ideas to it.

It's hard to give examples without writing pages and pages of example code. Some simple ones

There's no need to create interface classes in JavaScript because JavaScript doesn't care. As long as the function names match that's all it cares about. So it's super easy to write mocks or make more than one implementation of something.

  logger = {
    log: function(msg) { console.log(msg) },
    error: function(msg) { console.error(msg) },
  };

  dummy = {
    log: function() {},
    error: function() {},
  };

Need some data, just start typing

   var orge = {
     hitpoints: 27,
     shield: 34,
   };

   var dragon = {
     hitpoints: 653,
     shield: 57,
     immuneToFire: true,
   };

I didn't have to go define some class or struct first. I didn't have to know every minor variation I might need. Just write code

   function giveDamage(enemy, type, damage) {
     if (type === 'fire' and enemy.immuneToFire) return;  
     enemy.hitPoints -= damage;
   }

It's so gawd damn simple. In C/C++/C#/Java land I'd have to create a class, then start adding fields. If I added a field in the middle I'd have to go hand fix each and every static declaration. If I end up with too many fields that were only used by a few types of enemies each I'd have to start designing some specialized "extra" field that points to these specialty fields just so the main class isn't cluttered. .

I'd probably have to make it some specialized generic container and then get accessors like this

   void giveDamange(Enemy ememy, DamageType type, int damage) {
     if (type == DamageType.Fire) {
        BooleanField* b = emeny.GetExtraField<BooleanField>("immuneToFire");
        if (b && b->value() == true) {
          return;
        }
     }
     enemy.hitPoints -= damage;
   }

Now imagine all the code I needed to write to make GetExtraField. I'd need some base class "Field" which would probably be 10−20 lines of code. I'd need a collection class to hold them, more lines of code. I'd need some private untyped version of GetExtraField that returned a the base Field class. Then I'd need some templated type of GetExtraField I could pass in a concrete type, it would get the untyped field by name, check some "is" function if it's the correct type, if so downcast it to BooleanField and finally return a type safe concrete accessor. I'd probably have written 100−200 lines of code to do that. And worse, now I could no longer add them statically by typing a few lines of code Each one would have to be hand added at runtime in some init function. In JavaScript I don't care. All that disappears. Yea I know JavaScript is doing runtime initialization but it's mostly hidden in terms of what I actually have to type.

Now of course JavaScript sucks for those very same reasons. Every time I track down a bug and it's one that would have been caught at compile time in a strictly typed language it's kind of annoying. But, I wonder if there's some middle ground. Could we design a language that had all or most of the flexibility of JavaScript AND the some/most/all of type safety of C/C++/C#/Java and the speed of C or at least optionally.

For example C# has struct vs class. An array of structs in inline whereas an array of classes is actually an array of references to instances of the class. That seems like an interesting idea. Maybe we could have class, struct and bag or something where bag is like JavaScript's objects and class and struct are like C#?

Or maybe the compiler of this new language could do more global stuff so for example maybe without declaring types it could just guess them and then check at link or compile time they are all consistent.

   someFunc(a, b) {
     return a + b;
   }

Later I do

   c = someFunc(1, 2);

Oh, a, b, and c are ints so generate that function. If it sees

   d = someFunc(1.2, 3.4)

Then generate that. Maybe I could mark it as "must be consistent"

   someFunc!(a, b) {
     return a + b;
   }

Now if it's used inconsistently the compiler complains?

Note I'm speaking out of my ass. I have no idea if these ideas will work, if they already exist in other language, or what.

How about this

   f = {
     name: "orge",
     hitpoints: 120,
   }

Should that be enough to know that F is a

struct AnonStruct {
  string name;
  int hitpoints;
}

What about this?

   monsters = [
    { name: "orge", hitpoints: 120, },
    { name: "orc",  hitpoints: 20, },
    { name: "rat", hitpoints: 1, },
    { name: "dragon", hitpoints: 700, canFly: true, },
   ]

Can the compiler figure out that monsters should be defined as

struct AnonStruct {
  string name;
  int hitpoints;
  bool canFly
} monsters[] = ...

Another idea is what if the compiler could look at all uses and decide if it can make something a struct like the example above or if it needs to remain a bag. JavaScript runtimes or at least V8 actually does this but I'd like it to do it at compile time. Decide if it's a struct or a bag, pick one, then fail at runtime if it's not used that way maybe? Then optionally give it a way to enforce it at compile time so you can track down stuff. If you say X is a struct then the compiler can check. If you say it's bag then even if it thinks it could be a struct at compile time it leaves it a bag.

Anyway, I have no idea. I see Jonathan Blow is working on his language. I've talked to some language guys who say he's basically repeating what research found 20, 30, 40 years ago and doing it poorly. I'd like to know more about what they're talking about. I don't agree with J.Blow that GC is evil. Sure, I used to think that but even Unreal engine uses GC and tons of AAA games have shipped with it. Similarly tons of Unity games are shipping just fine with GC. Knowing when it's ok and when it's not is fine but forcing you to do everything manually because you don't understand GC is kind of stupid. It's kind of like saying everything should be written in assembly language. No, a few down to the metal things should be, similarly a few down to the metal things should not GC, but overall GC works just fine just like scripting languages and blueprint systems work well for certain classes of problems. Don't throw out the baby with the bathwater and all that.

Anyway, that was the long way of saying the point is going back to C/C++ is not fun for the reasons above. I feel like people who've never stopped C/C++ don't understand that. I was certainly one of them at one point but I've had my mind expanded ;)

Comments
The 4 stages of a C/C++ Programmer using JavaScript
Less Libraries more Code?