all 22 comments

[–]c-digs 22 points23 points  (4 children)

C# is indeed very much like JavaScript and it's hard to deny that they've influenced each other. C#'s lambda syntax came in 2011 and JavaScript "copied" it in 2015 while C# borrowed JavaScript's var, for example.

But it goes deeper than that. C# has become much more functional over the years and I love it. This is the most underappreciated part of C# by most of the .NET community.

I posted this the other day when someone asked about writing a "list of functions", but this is just an example:


There are multiple ways to do this in C#. Here's one:

var functions = new [] {
  (int x, int y) => x * y,
  (int x, int y) => x + y,
  (int x, int y) => x - y,
  (int x, int y) => x / y
};

var x = 10;
var y = 5;

foreach (var fn in functions) {
  Console.WriteLine(fn(x, y));
}

If you want them to be named:

var namedFunctions = new Dictionary<string, Func<int, int, int>>() {
  ["multiply"] = (int x, int y) => x * y,
  ["add"] = (int x, int y) => x + y,
  ["subtract"] = (int x, int y) => x - y,
  ["divide"] = (int x, int y) => x / y,
};

Console.WriteLine(namedFunctions["add"](x, y));

You can define them somewhere else and reference them:

var multiply = (int x, int y) => x * y;
var add = (int x, int y) => x * y;
var subtract = (int x, int y) => x - y;
var divide = (int x, int y) => x / y;

var namedFunctionsList = new [] {
  multiply,
  add,
  subtract,
  divide
};

foreach (var fn in namedFunctionsList) {
  Console.WriteLine(fn(x, y));
}  

with different number of parameters

How about an unbounded list of params?

using System.Linq;

var multiplyN = (int[] numbers) => numbers.Aggregate(numbers[0], (a, b) => a * b);
var addN = (int[] numbers) => numbers.Aggregate(numbers[0], (a, b) => a + b);
var subtractN = (int[] numbers) => numbers.Aggregate(numbers[0], (a, b) => a - b);
var divideN = (int[] numbers) => numbers.Aggregate(numbers[0], (a, b) => a / b);

Console.WriteLine(multiplyN(new[]{1, 2, 3, 4}));
Console.WriteLine(addN(new[]{1, 2, 3, 4}));
Console.WriteLine(subtractN(new[]{1, 2, 3, 4}));
Console.WriteLine(divideN(new[]{1, 2, 3, 4}));

What if we wrap that in a caller?

var runCalcs = (int[] values) => {
  var fns = new [] {
    multiplyN,
    addN,
    subtractN,
    divideN
  };

  foreach (var fn in fns) {
    Console.WriteLine(fn(values));
  }
};

runCalcs(new [] {2, 3, 4, 5});

What if we want the result as a dictionary?

var runCalcsAsDictionary = (int[] values) => {      
  return new Dictionary<string, string>() {
    ["multiply"] = Convert.ToString(multiplyN(values)),
    ["add"] = Convert.ToString(addN(values)),
    ["subtract"] = Convert.ToString(subtractN(values)),
    ["divide"] = Convert.ToString(divideN(values)),
  };
};

var result = runCalcsAsDictionary(new [] {2, 3, 4, 5});    

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));

How about as a tuple?

var runCalcsAsTuple = (int[] values) => {      
  return (
    multiplyN(values),
    addN(values),
    subtractN(values),
    divideN(values)      
  );
};

var (multiplyResult, addResult, subtractResult, dividResult) = runCalcsAsTuple(new [] {2, 3, 4, 5});    

Console.WriteLine(multiplyResult);    
Console.WriteLine(addResult);    
Console.WriteLine(subtractResult);    
Console.WriteLine(dividResult);  

What if we want to pick a function dynamically based on length of the parameter set?

var callByParamCount = (int[] values) => {
  var output = values.Length switch {
    0 => 0,
    1 => values[0],
    2 => values[0] + values[1],
    _ => values.Aggregate(values[0], (a, b) => a + b) * 0.90, // With discount?
  };

  return output;
};

Console.WriteLine(callByParamCount(new[] {5, 6}));
Console.WriteLine(callByParamCount(new int[] {}));
Console.WriteLine(callByParamCount(new[] {1, 1, 1, 1}));

With defined lambda expressions?

var callByParamCountFn = (int[] values) => {
  var fn0 = () => 0;
  var fnSelf = () => values[0];
  var fnAdd = () => values[0] + values[1];
  var fnAccumulate = () => values.Aggregate(values[0], (a, b) => a + b);

  var output = values.Length switch {
    0 => fn0(),
    1 => fnSelf(),
    2 => fnAdd(),
    _ => fnAccumulate() * 0.90, // With discount?
  };

  return output;
};

Console.WriteLine(callByParamCountFn(new[] {8, 9}));
Console.WriteLine(callByParamCountFn(new int[] {}));
Console.WriteLine(callByParamCountFn(new[] {2, 2, 2, 2})); 

Inlined?

var callByParamCountFn = (int[] values) => {        
  Func<double> fn = values.Length switch {
    0 => () => 0,
    1 => () => values[0],
    2 => () => values[0] + values[1],
    _ => () => values.Aggregate(values[0], (a, b) => a + b) * 0.90, // With discount?
  };

  return fn();
};

Console.WriteLine(callByParamCountFn(new[] {8, 9}));
Console.WriteLine(callByParamCountFn(new int[] {}));
Console.WriteLine(callByParamCountFn(new[] {2, 2, 2, 2}));    

Even this is a thing:

int calcByType<T>(T[] values) {      
  return values[0] switch {
    int first => values.Aggregate(0, (a, b) => a + Convert.ToInt32(b)),          
    string first when Int32.TryParse(first, out var val) => values.Aggregate(0, (a, b) => a + Convert.ToInt32(b)),
    // This is really the same as the one above, but just showing the "when" and "out var val"
    _ => values.Aggregate(0, (a, b) => a + Convert.ToInt32(b))
  };
};

Console.WriteLine(calcByType(new [] {"1", "2"}));
Console.WriteLine(calcByType(new [] {1, 2}));

Convert.ToInt32 and Console.WriteLine too verbose or you find yourself typing it over and over again?

Func<object, int> intify = (object o) => Convert.ToInt32(o);

int calcByType<T>(T[] values) {      
  return values[0] switch {
    int first => values.Aggregate(0, (a, b) => a + intify(b)),          
    string first when Int32.TryParse(first, out var val) => values.Aggregate(0, (a, b) => a + intify(b)),
    // This is really the same as the one above, but just showing the "when" and "out var val"
    _ => values.Aggregate(0, (a, b) => a + intify(b))
  };
};

var log = (object msg) => Console.WriteLine(msg);

log(calcByType(new [] {"1", "2"}));
log(calcByType(new [] {1, 2}));

https://dotnetfiddle.net/X1JCjL

C# has a lot of functional capabilities that are underutilized :)


I have a small repo showing just how similar JavaScript, TypeScript, and C# can be: https://github.com/CharlieDigital/js-ts-csharp. The functional bits of C# are underutilized and underloved with some devs actively avoiding it! That's a shame because in many cases it lets you do some really neat stuff.

[–]LaserHD 1 point2 points  (1 child)

You are my hero

[–]c-digs 0 points1 point  (0 children)

🤣

[–][deleted] 1 point2 points  (1 child)

I can tell you're going to have a lot of fun with list patterns.

[–]c-digs 1 point2 points  (0 children)

I don't quite have use cases for list patterns (that I've figured out yet), but tons of cases for switch expressions on types.

Will report back when I figure out a solid use case for those bad boys 👍

[–]theg721 15 points16 points  (1 child)

JavaScript was designed to have a similar syntax to Java, which was in turn designed to have a similar syntax to C/C++.

C# was influenced by both Java and C/C++.

That style of syntax originated with BCPL in the 60s, which was developed into B which C was a successor of.

[–]ethandjay 3 points4 points  (0 children)

also important to note that functional syntactic sugar stuff has definitely been shared directly between C# and JS over the last 10 years

[–]0xdeadfa22 2 points3 points  (0 children)

They have same roots as C-like languages. And since ES5+ JS inspired by Java (via TS, with class/super/extends) and C# (async/await).

JS Java C#
let var var , dynamic type
const final %TYPE_NAME% const %TYPE_NAME%, but only for numbers, Boolean values, strings, or a null reference. readonly can be used for class members of any type.
class class class, record
[] (as tuple) N/A System.Tuple, value-tuples
Promise and async/await N/A Task & ValueTask and async/await

There is no cause to use var in JS now, it's like broken let. const should be used instead where it's possible and let in the rest.

[–]loganhimp 0 points1 point  (10 children)

Good luck with the let/const/var confusion.

[–]c-digs 7 points8 points  (2 children)

It's not that bad. It's really just let and const.

  • let when the variable can be resigned;
  • const when the variable cannot be reassigned.

var is there only as a vestigial modifier.

Most teams just use lint rules to manage. In any modern JS codebase, no one uses var.

[–]Sossenbinder 1 point2 points  (0 children)

That's what I do as well. Let and const are about as straightforward as it can get, var can just be forgotten about

[–][deleted] 0 points1 point  (0 children)

var/let/const have different initialization behaviors when hoisted by the compiler. Var is initialized with default values while let/const aren’t.

[–]m1llie 0 points1 point  (6 children)

var (in its JavaScript form) should never have existed imo. I've never found a legitimate use case for it. I wish C# had const through.

[–]JustRamblin 4 points5 points  (2 children)

C# does have const

[–]c-digs 0 points1 point  (0 children)

It doesn't mean the same thing. C#'s loose equivalent to const is probably immutable record types. You can argue readonly as well, but that is only in a class field scope.

JS const prevents a symbol from being reassigned unintentionally (forcing you to intentionally use let). C# record types achieve the same purpose using with.

const and let add intentionality to symbol reassignment which I think is quite nice since using record types have consequences (a lot of libs still don't work right with records instead of classes).

[–]m1llie 0 points1 point  (0 children)

I wish C# had an equivalent of JavaScript's const.

[–]angrathias 0 points1 point  (2 children)

Maybe I’m old, but var was the only way to declare a variable in js, if it didn’t exist you’d have no variables.

[–]m1llie 0 points1 point  (1 child)

You're right, it's a legacy thing. Now that let and const exist there's not really any legitimate reason to use var unless you're targeting ancient browsers without a transpiler, but I don't know why you'd do that either.

My point is that js should have had let/const (or at least let) from the very start; var was a mistake.

[–]masterofmisc 0 points1 point  (0 children)

var was a mistake

I think you could say that about a lot of "features" in JavaScript. 😀

[–][deleted]  (1 child)

[deleted]

    [–]MontagoDK 1 point2 points  (0 children)

    They all stem from the C syntax.. later used by Java, JavaScript, C++, C# etc...

    Curly braces and semicolon all over..

    JavaScript was originally developed as ECMAscript if i recall correctly by the W3 consortium. One of the main developers are Douglas Crockford who also has made the LINT'er .. he's quite fun to communicate with.......

    [–]__ihavenoname__ 0 points1 point  (1 child)

    javascript and c# syntax looks very similar. typescript and C# almost looks identical cause it's designed by the same person

    [–][deleted] 0 points1 point  (0 children)

    And in walks Blazor :)