More JavaScript vs C#/C++

2015-11-16

I ranted before on JavaScript vs C#/C++ and for some reason I was in the mood to revisit that.

Basically the last 2 years I've been mostly in JavaScript land. Like most C++ programmers I hated it at first. But now I find it hard to have any desire to switch back. Of course I'll program in whatever language I need to to do the job. C++ if I'm Unreal. C# if I'm in Unity.

But, kind of like J−Blow mentioned in his first few videos it's frustrating every time there is busy work to do in C++ or C# that there is not when in JavaScript. To be clear he only mentioned his frustration with C++. AFAICT he has no serious experience with JavaScript. I expect he's at stage #1 or stage #2 at best

I'm not by any means saying JavaScript is awesome. The worst part is the no−errors on mis−spellings. Just 2 days ago I had a property, s.inIframe but had mis−spelled it somewhere else as s.isIFrame, spent 5 mins or more tracking down why it wasn't working. I don't know if it was only 5 mins. It felt more like 25. When I finally found it I changed it to s.inIFrame and spent another few mins until I realized it was still wrong. The F was supposed to be lowercase.

At the same time it's so liberating to just add stuff. I'm sure I going to get dumb ass responses to this example so before you rant on the example I would never write a function called drawBox like this. It's an example. Please try to grasp the point being made rather than go anal on the specifics.

Let's say I have a function

function drawBox(box, x, y) {
  ctx.fillRect(x, y, box.width, box.height);
}

drawBox(someBox, 10, 20);
drawBox(someOtherBox, 30, 40);

Let's say I want to add a scale. Because I realize the number of options I might want will likely increase I'll add an options argument

function drawBox(box, x, y, options) {
  options = options || {};  // make it so code below is simple if no options are passed in
  var scale = options.scale || 1;
  ctx.fillRect(x, y, box.width * scale, box.height, * scale);
}

drawBox(someBox, 10, 20);
drawBox(someOtherBox, 30, 40, {scale: 2});

Tomorrow I decide I want to pass a color in. So I just add it to options

function drawBox(box, x, y, options) {
   options = options || {};  
   var scale = options.scale || 1;
   if (options.color) {
     ctx.fillStyle = options.color;
   }
   ctx.fillRect(x, y, box.width * scale, box.height, * scale);
}

Or I can do this

function drawBox(box, x, y, options) {
  options = options || {};  
  var scale = options.scale || 1;
  ctx.fillStyle = options.color || "red";  // use red as the default color
  ctx.fillRect(x, y, box.width * scale, box.height, * scale);
}

Using them

drawBox(someBox, 10, 20);
drawBox(someOtherBox, 30, 40, {scale: 2});
drawBox(someBox, 50, 60, {color: "purple"});
drawBox(someOtherBox, 70, 80, {scale: 1.5, color: "purple"});

It's so damn easy.

Compare to say C#/C++. I'd have to do something like this. I start with DrawBox

struct Box {
   public Box(float _w, float _h) {
       width = _w;
       height = _h;
   };
   public float width;
   public float height;
};

void DrawBox(Box box, float x, float y) {
   ctx.fillRect(x, y, box.width, box.height);
}

Then I decide I want to add scale so

struct DrawBoxOptions {
   // Do I make a constructor? 
   float scale = 1.;
};

static DrawBoxOptions s_defaultBoxOptions = new DrawBoxOptions();

void DrawBox(Box box, float x, float y, DrawBoxOptions options = null) {
   if (options == null) {
     options = s_defaultBoxOptions;
   }
   ctx.fillRect(x, y, box.width * options.scale, box.height * options.scale);
}

Using it is now far more painful than it was in JavaScript

drawBox(someBox, 10, 20);
DrawBoxOptions opt = new DrawBoxOptions();
opt.scale = 2f;
drawBox(someOtherBox, 30, 40, opt);

then I decide I want to add color so

struct DrawBoxOptions {
   // Do I make a constructor? 
   public float scale = 1f;
   public string color = "red";
};

static DrawBoxOptions s_defaultBoxOptions = new DrawBoxOptions();

void DrawBox(Box box, float x, float y, DrawBoxOptions options = null) {
   if (options == null) {
     options = s_defaultBoxOptions;
   }
   ctx.fillStyle = options.color;
   ctx.fillRect(x, y, box.width * options.scale, box.height * options.scale);
}

this part is getting really crap

drawBox(someBox, 10, 20);
DrawBoxOptions opt1 = new DrawBoxOptions();
opt1.scale = 2f;
drawBox(someOtherBox, 30, 40, opt1);
DrawBoxOptions opt2 = new DrawBoxOptions();
opt2.color = "purple";
drawBox(someBox, 50, 60, opt2);
DrawBoxOptions opt3 = new DrawBoxOptions();
opt3.color = "purple";
opt3.scale = 1.5f;
drawBox(someOtherBox, 70, 80, opt3);

I suppose I can make a constructor for DrawBoxOptions and then used named parameters. Unfortunately I don't think I can use the same names like this

struct DrawBoxOptions {
  public DrawBoxOptions(float scale = 1f, string color = "red") {
    scale = scale;
    color = color;
  }
  float scale;
  string color;
};

So either have to come up with some convention, like arguments always start with _ or else I need to make getters (and setters?) This might work

struct DrawBoxOptions {
  public DrawBoxOptions(float scale = 1f, string color = "red") {
    m_scale = scale;
    m_color = color;
  }  
  public float scale {
    get: { return m_scale; }
  }
  public string color {
    get: { return m_color; }
  }

  float m_scale;
  string m_color;
};

Now I can do this

drawBox(someBox, 10, 20);
drawBox(someOtherBox, 30, 40, new DrawBoxOptions(scale: 2f));
drawBox(someBox, 50, 60, new DrawBoxOptions(color: "purple"));
drawBox(someOtherBox, 70, 80, new DrawBoxOptions(color: "purple", scale: 1.5f));

That's close to what it was in JavaScript but look at all the contortions I had to go through to get there. It's those contortions that make me wish for a new language that is somewhere in between the 2 extremes.

I'm sure I can come up with other examples. The one that made me write the first rant was this. I have an event emitter in JavaScript

function EventEmitter() {
  var handlers = [];
  this.on = function(event, handler) {
    handers[event] = handler;
  }
  this.emit(event) {
    var fn = handlers[event];
    if (fn) fn.apply(null, arguments);
  }
};

That's it. I can now write code like this

var emitter = new EventEmitter();

// if mouse was clicked
emitter.emit('mouseclick', mouseX, mouseY);

// if we lost the focus
emitter.emit('focus', false);

// if we regained the focus
emitter.emit('focus', true);

Some user of our system can do this

emitter.on('mouseclick', handleMouseClick);
emitter.on('focus', handleFocusChange);

function handleMouseClick(event, mouseX, mouseY) {
  console.log("mouse was clicked at: ", mouseX, mouseY);
}

function handleFocusChange(event, gotFocus) {
  console.log("we " + (gotFocus ? "gained" : "lost") + "the focus");
}

Now consider how you'd have to implement that in C# or C++. First off to implement Emitter.on you're going to need some kind of template system because on's second argument is always a different kind of function. handleMouseClick and handleFocusChange have different signatures. Second you're going to have to go through some contortions to get Emitter.emit to work because it always takes different arguments.

The way I solved it in C# was to make emitter by something like

class Emitter {
  public delegate void TypedEventHandler<T>(string eventName, T data);
  
  public void On<T>(string eventName, TypedEventHandler<T> handler) {
    TypedEmitterHelper<T> helper = new TypedEmitterHelper<T>(handler);
    RegisterHelper(eventName, helper.Emit);
  }
  
  public void Emit(string eventName, object data){
    EmitterFunc func = null;
    if (m_handlers.TryGetValue(eventName, out func)) {
      func(eventName, data);
    }
  }
  
  private delegate void EmitterFunc(string eventName, object data);
  
  private void RegisterHelper(string eventName, EmitterFunc func) {
    m_handlers[eventName] = func;
  }
  
  private class TypedEmitterHelper<T> {
    public TypedEmitterHelper(TypedEventHandler<T> handler) {
      m_handler = handler;
    }
    
    public void Emit(string eventName, object data) {
      T typedData = &&#0035;128241;data;
      m_handler(eventName, typedData);
    }
    
    TypedEventHandler<T> m_handler;
  }
  
  private Dictionary<string, EmitterFunc> m_handlers = new Dictionary<string, EmitterFunc>();  
}

This only handles handlers that take 1 argument so I have to make types

class MouseClickEventData {
   public MouseClickEventData(float x, float y) {
     mouseX = x; 
     mouseY = y;
   }
   public float mouseX;
   public float mouseY;
}

class FocusEventData{
   public FocusEventData(bool _gotFocus) {
     gotFocus = _gotFocus;
   }
   public bool gotFocus;
}

Now you can create your handlers

void HandleMouseClick(string eventName, MouseClickEventData data) {
   Console.Writeline("mouse was clicked at " + data.mouseX + ", " + data.mouseY);
}

void HandleFocusChange(string eventName, FocusEventData data) {
   Console.Writeline("we " + (data.gotFocus ? "gained" : "lost") + " the focus");
}

And you can register them with

someEmitter.On<MouseClickEventData>("mouseclick", HandleMouseClick);
someEmitter.On<FocusEventData>("focus", HandleFocusChange);

And emit with

someEmttier.Emit("mouseclick", new MouseClickEventData(mouseX, mouseY));
someEmttier.Emit("focus", new FocusEventData(gotFocus));

So much code!

Now you might say "yea but I gain a tiny bit of type safety by adding all those contortions". I suppose that's true to some extent although the code above is not compile time typesafe, only runtime. Making it compile time typesafe would require several more contortions like embedding the event name into the EventData classes or something so you can't use the wrong handler with the wrong event. But, as J Blow has said, the odds of most "good" programmers getting that wrong are pretty low. Like he said in his JAI design he's not designing the language to save bad programmers from doing bad things. He's designing for good programmers for the language to get out of their way.

To get more concrete it's not likely some programmer is going to write

function handleFocusChange(event, mouseX, mouseY) { ... // O'RLY?

To make that C# emitter even more like the JavaScript example so that it would handle handlers and emitters that take multiple arguments would require even more contortions. Making adaptors for 2 args, 3 args, 4 args etc....

I have a feeling a lot of C++ and C# programmers get enjoyment out of solving the puzzle of writing things like Emitter above. I know I used to. It feels good to figuring out how to get C++ or C# to do something that makes writing code later simpler, designing adaptors and templated systems is often a challenge and there's certainly a feeling of accomplishment from making one. But, having used a language where I rarely need to go through those kinds of contortions I start to see it as a frustrating waste of time. I really want to get to my actual project and not this sub project that's in the way.

Like I pointed out above I don't like it when I spend 5−20 minutes tracking down a typo in JavaScript that I wouldn't have had to trackdown in C++/C#. But the faf I have to type in C#/C++ drives me nuts.

It's frustrating not being able to pass on the joy of a JavaScript to people who haven't used it much. Mostly they're just frustrated with the parts that are different from what they're used to. And again I'm not advocating JavaScript. I'm advocating like J−Blow, a language that's a joy to use where part of joy means never having to type more than is absolutely necessary. Unfortunately I don't think JAI is it because J Blow wasn't been down this path. He's still in C++ land and JAI while awesome is not benefitting from being this far on the other side. On the other hand maybe the 2 paths are incompatible? I hope not.

Comments
More Vertexshaderart.com
Writing Good Documentation and Examples