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 = &#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.