It's more expressive in that it's more easily testable... To write testable code in C# you generally need to introduce complex classes, interfaces and IoC/DI into a project. JS allows you to work around this with generally less complexity of code. For the most part node/es6-style modules can be far more simple and easy to reason about than C# projects tend to be.
Don't get me wrong here, I like a LOT about C# but expressiveness compared to JS doesn't even come close, when you include the pending ES7 stuff via Babel.
Uhm, I'm not sure you correctly understand the point of Inversion of Control and Dependency Injection if you think that you don't need it in JS.
ES "let's you work around this" in that it let's you just do stupid shit until it blows up in your face. Statically typed languages force you to consider those scenarios that might lead to blow ups. You still have to consider those scenarios in ES, but the language doesn't force you to, so you have to have the diligence to do it yourself.
"Expressiveness" doesn't mean "write as little code as possible to get the base case running". Expressiveness means the ability to implement different patterns of solutions with native syntax. C++ is "more expressive" than C when it comes to object oriented programming, not because C cannot do OO, but because C++ has specific, checked syntax for it.
I'm not saying don't test... you can write modular code in JS... in fact you can export a single function as a JS module via node/browserify... take babel and you get es6/7 features. Use mocha/chai/sinon/proxyquire and you can inject dependencies into your modules for testing, without having to write weird interface abstractions in your code...
References are consistent and one-way, you no longer have to deal with searching for where a given dependency is coming from... Or where the implementation is... references are always direct this way.
C#:
using MyOrg.Base.Interfaces;
public class Foo {
public IBar Bar {get;set;}
public string Baz() {
//wth is IBar.Baz implementation come from?
//damn, now I need to search the project for IBar
return this.Bar.Baz(this);
}
}
JS
import bar from '../lib/bar';
export default function baz(context) {
return bar.baz(context);
}
In the JS case, you know where the implementation of bar comes from when looking into code, trying to resolve a bug. You can also use proxyquire in order to test the module, replacing bar with a shim for the purposes of testing... in the JS case the code is still clean, and you don't have a couple of layers of indirection abstracting you away from finding the next piece in the puzzle. And it doesn't limit your ability to test.
I don't know what you mean by "weird interface abstractions". Interfaces are how you test the edges between modules in statically typed languages. They are the contracts that get checked at compile time to make sure you're not sending IPAddress-es where DateTime-s are expected.
And I don't know what that last part is supposed to mean. I've never not know "where my dependencies are coming from" or "where the implementation is". It's not like they live in some hidden place that I can't access. I reference them directly and explicitly.
EDIT: you ninja-edited on me. No, you are not at a loss for where IBar is defined. It's extremely easy--a single key press--to find it. Just hit F12 on it. You'll go right to its definition.
Yeah, that takes me to the declaration of the IBar interface, but not to the implementation that's being used... The worst case is often when you have dependency injection along with something like EntLib's Data application blocks... it's a mess finding things on a project that you've been tossed in the middle of.
I'm not saying these patterns should never be used... only that there are generally simpler expressions that should usually be favored.
Having done a lot of reflection in both JS and C# (well, .NET really, it's a library and runtime feature, not a language feature), I think I can confidently say that .NET is light years ahead of JS when it comes to reflection. I don't think I've seen a system better than .NET's. Because of the way it is built, you can still write type safe code. That's impossible to do in JS.
I think people forget that, in JS, the types still exist. Just because it's not statically typed doesn't mean you don't have to think about types and what types are appropriate for given scenarios.
With JS, it's impossible to tell just from inspecting a reference to a function what it expects from you and what you can expect to give back. You have the length property to tell you the number of arguments and that's it. Of course, that's assuming it's not using a variadric arguments pattern, in which case the length value only tells you the minimum number of parameters expected, but not that more are possible.
Even if you can make reasonable assumption about the number of parameters, you won't be able to tell at all what types the function expects for those parameters. Want to list all the functions in a class that take two numbers and return a different number? Can't do it.
But still, if you could make reasonable assumptions about the type of things that go in and out of the function, you won't know how to even call the function. Does the function expect to be called as a method of a class? Does it expect to be called as a static function? The best you can hope for is to toString the function and check if the source includes the word "this". Here's hoping it's one written in JS itself and not one that is implemented internally in the browser.
Now before anyone says "why would you ever need anything like that?", it's an absolute necessity for building any sort of user-driven or data-driven reporting system. You need to be able to tell when your data set matches the functions you want to apply to them, at run-time, because the data isn't available at design-time. It's the classic reflection use case.
As far as I'm concerned, safety-guaranteed reflection is impossible in JS, unless you throw away JS functions almost completely and create your own Functor class with all the extra type information that you would need.
Or you could, you know follow either a naming convention or a parameter convention... For that matter, you can have each method simply test the data and return null if it doesn't apply and the resolved value if it does, or any number of other patterns...
For that matter, you can use an object stream, and pipe it all through to resolution. If your data comes from variant sources (spreadsheets, dirty xml, etc) then you are way better off with a scripted language.
Try importing a WSDL that defines various interfaces to use "Object" and VS/.Net chokes on it.
As far as users go, without the hard enforcements you can coerce values into either what you expect or cleanly drop out... unlike .Net where you have to go through the Try versions of convert, on who knows how many types in order to get something resembling predictable values... that doesn't even include regular expression syntax as a first class concept in JS.
I've handled dynamic input sources from both .Net (VB.Net and C#) and I'll tell you that most of the time JS/Node is simply easier... I've replaced complex importers written in .Net with straight forward, easy to reason with JS importers that run in Node, and edit/run/deploy without a compile step.
I will say that I do like that VB.Net has XML literals which can be very useful... but now that JSON is getting to be more common JS is less of a disconnect.
Naming conventions are a great way to end up with liar-code--code that isn't what it calls itself. Coercing user input into expected types is a good way to get exploits. Testing parameters explicitly is just asking for someone to forget a particular parameter, and just returning null instead of an error is how we end up with black-box functions that nobody can figure out why they get called in different places because they don't seem to have any particular use. WSDL is a different issue entirely; now you're talking about RPC (of which WSDL is just one format that .NET supports), which is not the same thing as reflection.
The things you're talking about doing, they are bad software design.
'Expressive' means that it's easy to write code that's easy to understand.
JS doesn't even come close to the expressiveness that you get in C# due to the fact that in JS, you don't even know the type of arguments being passed in any given function without looking elsewhere in the code - therefore, it's not easy to understand.
If you're going to go with the definition that says expressiveness means "the variety and quantity of ideas that can be expressed", Javascript also loses there since there are whole classes of behavior that cannot be practically expressed in Javascript. For instance - try writing a function that only accepts a standard single precision floating-point number as it's argument and see how much work you have to do compared to other languages.
Try expressing a Dictionary, Linked List, HashTable, SortedList, SortedDictionary, etc in JS - you'll be writing those structures yourself or cobbling together some random lib. Better languages let you actually express those things without having to write them yourself.
Don't get me wrong here, I like a LOT about C# but expressiveness compared to JS doesn't even come close, when you include the pending ES7 stuff via Babel.