It’s strange what people put up with in C#
gist.github.com
gist.github.com
"Don Syme: My main responsibility is the design and implementation of the F# programming language. I've also worked on the design of virtual machines, the C# language (being co-responsible for C# and .NET generics, and one of many contributors to C# asynchronous programming) and, indirectly, Visual Basic and other .NET languages."
History, by way of the C# design team, has validated his opinions. Don Syme has had a massive influence on C# even when one degree removed from it's design. His complaints should be considered carefully.
Mark Seemann has a good write up about his contributions: https://blog.ploeh.dk/2015/04/15/c-will-eventually-get-all-f...
That being said, not everything is a fit (otherwise what's the point of F#?) and much of the post here seems to be edge cases or personal preference than C# defects.
I also don't understand how you can have a "return true" for a function that's supposed to return a formatted value.
I don't know what a C# notebook is, but the difference between a statement and an expression is made in any imperative programming language I know. However, the example is very strange; why would putting a semicolon at the end of the line make any difference? Why should it even compile without it?
As for the ICompare interface, I think the author has a point. The example class they give is unnecessarily complex, but they're right that the lack of concise inline class definitions can sometimes be annoying. However, I'm pretty sure you can use lamba functions to do the exact same thing the author is showing in F#, down to storing the comparator as a Func<> variable.
The lack of HTML generation is also a weird one. C# is not a template language and is not intended to be one. You can use ASP to generate HTML pages fine, but I don't understand the value of a DSL to generate HTML in pure C#. Is the loj 't that the syntax can't be radically reformed? Because I consider that to be a good thing.
The point I take away from this rant is that the author is trying to use an imperative language with some functional language features as a fully functional language. These aren't shortcomings of C#, this is the result of using the wrong tool (or using the tool wrong) for the job.
There is Comparer.Create convenience method which creates an IComparer instance from a lambda. But yeah it is a workaround.
As far as "language gotcha" articles go this is pretty weak IMO
The C# compiler doesn't; the F# compiler does.
A. (1,4) C# immutable collection support is a step-child, without full support for the required behaviours around it from the compiler and the community. That's why VS allows "immutableList.Add(item)" as if it were mutable, when it should be an error.
Yea, C# isn't just a functional language, but if there's support for immutable collections it should be done right.
B. (2,3) There is "no way to implement interfaces or abstract classes using an expression". The author's example is simple, but one could imagine a comparison involving captured variables where the separate class complicates things, because any captured variable needs to be shoved in a constructor.
I've hit 1 and 2 directly, 3 comes naturally from 2 (when you can define an object expression, you want a simple syntax for the constructor), as well as 4 from 1. I've had sufficient annoyances with 1 I've decided to not use immutable collections until support is improved.
As for dynamic, it's good for what it does, people who use it know the risks. Instead, I'd complain about nuget, when it's too easy to use outdated/obsolete packages and keep them that way. npm actually has better package management options. The saving grace is that the typical C# solution has far less dependencies than the JS/TS projects, but it's best to act prophylactically, and help the programmer to keep dependencies updated.
Tho, your own example of immutable collections is actually a good one. Rather specific, but still good. Immutable collections really could profit from compuiler-level warnings or even errors. It would be an easy enough fix for the language/compiler team: have some attribute that you/the immutable library authors would decorate functions with, something like [NoDiscardReturn] and the compiler would check that and emit warnings/errors.
(2) Implementing something like IComparer<> is something you rarely do, and more or less never the way the author does.
var enumerator = array.OrderBy(e => e.Foo).ThenBy(e => something.CheckProperty(e.Bar)).ThenByDescending(e => e.Name, StringComparer.OridinalIgnoreCase);
array = enumerator.ToArray();
// Tho you often do not need to call .ToArray() if you're only gonna traverse the enumerator once
Using Linq like this is more idiomatic and also kinda "functional", which appears to be what the author is after.Now, this was specific to the comparer stuff, but in general a sane API these days would use Func<> or Action<> instead of single-method interfaces. There are still some leftovers in the C# stdlib (and probably more in third party libraries) but again, you can write a wrapper once - not the horrible mess the author proposed either, but an interface implementation that would use single Func<> parameter in the constructor and then call that in the method implementation.
internal class Foo
{
public string Bar;
public int Baz;
public override string ToString() => $"{Bar}: {Baz}";
}
internal class DontAnnoyMeComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> cmpFunc;
internal DontAnnoyMeComparer(Func<T, T, int> cmpFunc)
{
this.cmpFunc = cmpFunc;
}
public int Compare(T x, T y) => cmpFunc(x, y);
}
static void Main(string[] args)
{
var foos = new[] {
new Foo { Bar = "World", Baz = 1 },
new Foo { Bar = "Hello", Baz = 2 },
new Foo { Bar = "Hello", Baz = 1 },
};
// First with some ideomatic Linq
Console.WriteLine(string.Join(", ", foos.OrderBy(f => f.Bar).ThenBy(f => f.Baz)));
// Then with our helper interface implementation
Array.Sort(foos, new DontAnnoyMeComparer<Foo>((x, y) => {
var res = x.Bar.CompareTo(y.Bar);
return res != 0 ? res : x.Baz.CompareTo(y.Baz);
}));
Console.WriteLine(string.Join(", ", (object[])foos));
}
// Output
// Hello: 1, Hello: 2, World: 1
// Hello: 1, Hello: 2, World: 1
(3) related directly to (2). The only real reason this is a real-world problem for the author is that they do not actually write idiomatic code.(4) Sure, something like that would be nice sometimes. But the lack of it is hardly the end of the world. Tho on the flipside, in my opinion languages that offer such pseudo-DSLs or even real embdeddable DSLs quite often suffer from people writing DSLs for every freaking thing, thus reducing readability of their code.
(5) dynamic is hell for robust code, that's why you avoid it. I only ever used it when I was too lazy to do the extra typing to create proper types in run-once code dealing with JSON. And even then I should have probably just:
class MyType {
[DataMemeber]
public int Foo;
[DataMemeber(Name="baz")]
public int Baz;
}His item 4 is to some extent a result of the limited immutable support (he mentions "a lightweight way of building immutable list/collection expression"). Fix that support and 1,4 out of his list are not much of a problem anymore or at least ameliorated.
2) I would be glad to never need hear of IComparer or IEqualityComparer again, which I only need due to the stdlib leftovers... Admittedly not much, but enough to be annoying. His solution is more general, but your idea is very nice. Why didn't I think of that?
5) Dynamic is there for the Dynamic Language Runtime and for dynamic code far more general than your example. It should be kept in that silo, but it does do its job there.
IMHO, the author tries to write C# with the current trend of functional+immutable, and that's why he ended up hitting these issues. It's true that the OOP way isn't bad (at least with those examples), but it's also true C# could offer better support for doing things the other way without much effort.
And while you're at it dear C# team, add .Distinct<TSource, TDest>(Func<TSource, TDest>, IEqualityComparer<TDest>) similar to OrderBy and friends, and various constructors to the generic collection types taking Func<T, T, int> instead of IComparer<T>. Yeah, API bloat, but good one in this case IMHO.
C# and the .NET runtime and the compiler(s) are still evolving indeed, and I would guess a lot of issues will be addressed and fixed in the future.
PS: the most annoying bit to me about the immutable collection library is that they are dead slow whenever I tried them.
someList.Add(obj); someStr.Format(„%d“);
While the first one cleary modifies the list someList, the second one only _returns_ the modified string and someStr’s value does not change. IMHO fornatted text is a different (new) object therefore I don‘t see his issue, and I also think there‘s a shift towards immutable, which would clear such a thing up.
I don‘t see any semicolon missing.
To be honest, I have no idea what he's expecting with that "return true". How would that be different in any other language?
C# at this point is already probably 'larger' as far as feature set goes other mainstream languages.
In C# we don’t like functions with more than a few arguments. If you have 10 fields in your class, a constructor with 10 arguments is not appropriate. Instead we prefer object initialisers, but this requires exposing the ‘set’ method, and allowing mutability. This is resolved in C# 9, with init-only properties.
Why not have the ctor accept a single options object? At least if initialization is your only reason for allowing mutable props.
The author seems to be trying to fit a C#-shaped peg into an unsuitably academic F# hole.
C# is one of the best languages I've ever used, it's amazing how well it holds up in my (admittedly large) niche. But one language can't be everything to everyone.
For example, the complaint that something doesn't print in a C# notebook if a semicolon is appended seems so far out there in the margins of usefulness I'm having trouble finding back the page.
A great example is C# 8's Nullable Reference Types feature, touted as (partial) solution to the Null object references. But it isn't air tight, or even water tight. Even if you annotate everything correctly, you can still slip null refs past the compiler.
But F#'s solution to the Null ref issue is much simpler, don't allow nulls at all. Force the developer to use Option<T> to express the absence of value. F# did it correctly the first time and has benefitted ever since.
All calls can have side effects, even calls that return a value. And while C# did recently add the discard operator _ to throw away returned values out of an e.g. Tuple, if you did as this critique suggests and flag every "discarded information" the language would become verbose/obnoxious.
For example:
try
{
[....]
myFirstDatabaseTransaction.Commit(); // Compiler warning: "Discarded Information" (it returns the transaction ID, but has the side effect of committing your changes to the database).
}
catch([...])
{ [...] }
Basic programmer errors are easily caught during development, because they're immediately wrong. Unlike null reference exceptions which sometimes can only be found after extensive usage of the system (due to different states, or underlying data changing).Moreover that examples the author did are likely both oversimplified and wrong.
In the first one, a bool then a string is returned which should raise a type error in most cases (unless you do an anonymous function and feed the result back into something that accepts an object-typed parameter/use an object-typed variable to store the result).
I don't even know what's up with the second one; that's simply not valid code - if it was a normal function you cannot omit the trailing semicolon, and if it was a anonymous lambda you cannot have a trailing semicolon.
So I do not agree with OP at all. C# is a language capable of creating really beautiful and easy to understand code. It has generally very few quirks. Please don't turn it into JavaScript or F#.
I have yet to see a 'best of everything' language and C# has proven to be an amazingly versatile language. Yes, it has plenty of warts but so does everything else.
2) This is a bigger deal than a lot of people here seem to think.
3) As Don knows, this is getting addressed in C#9 (finally!)
4) I mean, you could do pretty much all of this with standard LINQ operators and AddToFront (which I end up adding into every project). So I think the example is garbage. What it's really missing is F#'s yield!
5) Yeah, but it's not like anyone uses it.
Funny that the counterexamples are from F# which also runs on the dot niet platform? And you could concievably mix and match them in the same project?
> in a C# notebook
Anyone has any idea what they mean here?
https://github.com/dotnet/interactive#notebooks-with-net-cor...
1. I don't understand this at all. You presumably typed your function return to expect whatever value.Format() returns, presumably a String. You accidentally returned a boolean, which ought to be a type error. But your complaint is that the compiler didn't yell at you for discarding the result of the Format method. Huh? And I don't know and have never used the notebook thing he's talking about. If he has an issue with how that works, maybe it's a problem with that, and not the C# language.
2. Yeah that particular interface is annoying. I guess you could add a bunch of syntax sugar to make it easy like that, but the modern C# way seems to be more about using lambdas and the standard Linq extension methods to sort things. I can't get much enthusiasm about adding a bunch of new syntax sugar to make using the kind of archaic interface model smoother when there's already alternatives that work more like the way he wants.
3. Okay that would be kind of cool, and I think they already did add it, or plan to soon. I don't think it's enough of a benefit to be a major factor in choosing a language though.
4. I don't really know this IHtmlContent style of creating HTML. To me this seems like another case of, I guess that would be kind of cool, but I think there are better solutions for that already. I honestly would rather reach for whatever the standard HTML template format is for any language before using some object creator syntax sugar like that. It seems like that would be much nicer than any object creator format once you start adding in classes, IDs, formatting, etc.
5. Don't really know what he means here. dynamic is a thing all right. IMO, it's a handy thing for a few very special cases, and best kept as isolated as possible. For sure you can do some really horrendous things if you use too much dynamic everywhere, but it's on you to not do that. If you really wanna write dynamic code, best to switch to Ruby, Python, Javascript, etc. I suppose it's a good thing that it has it, just to give you the option if you really need it. Does he object to it existing at all, or to it being overused in some specific piece of code?
- satisfying contracts using delegates/lambdas rather than interfaces
- using higher order fn's rather than indexed loops, etc.
which he's obviously used to doing in F#, so he can do it from C# as well. If he rants on C# like this, I'd hate to say what he has to say about Java which, as someone said is not object-oriented but "class-orientated".
It's hard to believe that D.S. wrote this -- esp. since C# has borrowed so much of F#'s ideas, and you can just write F# if you like, or use both. I've been using F# since it was first released, and it's great, but certainly not perfect either. IMHO, C# is one of the best all-around general-purpose languages with "familiar" syntax around with 1st class tooling and ecosystem. All it needs is records w/structural equality semantics, DU's and implicit "new" in object creation. F# on the other hand hasn't moved the it's needle much in a long time. (Like using Rosyln as a back end, supporting higher-kinded modules, etc.)
I also found myself pulling my eyes out because I kept trying to do const-correctness but it doesn't seem to support it at all (which was really odd given that this was inside a game engine)
Return boolean | string? Perhaps they're thinking of TypeScript :-D
1. Structural (TS) typing is 1,000x better than nominal typing (C#, Java, most other mainstream languages)
2. Union types are incredibly useful and reduce a lot of boilerplate and overloading.
With structural typing, you can have types that satisfy a type requirement incidentally. This helps you to write in less coupled code.
For example, let's say you have the `walk(Walker walker, int distance)` function. With nominal typing, you have to create the `Walker` interface, class, or trait, and then anything that can walk explicitly implement/extend it.
With structural typing (let's use TS as an example), you can say, `type Walker = {legs: Number}`. Then any object with a property called `legs` with a `Number` type value will satisfy the first argument of my `walk` function.
This is a trivial example, but it becomes extremely powerful and useful in large code bases. The compiler can start telling you exactly how you need to refactor your code to make it work again. And you don't have to keep every data structure in your head when you're designing your code. You can just say, "OK, this object is a cat. It has fur, legs, and a tail." It can be a simple data structure, without worrying about how it relates to other objects and methods.
I like the Rust feature of semicolon discarding information. Is that not what is happening here? Really contrived example that he accidentally wrote "return true".
I actually made a post on the Rust subreddit inquiring why Rust made must_use opt-in instead of opt-out like Swift and, apparently, F#. The reaction was OVERWHELMINGLY negative toward the idea of it being opt-out, which I find very surprising considering the kind of code one typically sees in Rust.
I think i've seen clean implementations in F#, but not in C#. The only thing that prevents me from fully embracing Unity is not knowing how to do pure function reducers with a single store. That, and an aversion for everything being so class-heavy. (My favourite languages these days are elixir and typescript)
select new DynamicObject(new
{
A = value.A,
B = value.C + "_" + value.D
}).CreateDuck<DummyInterface>();> If you did as this critique suggests and flag every "discarded information" the language would become verbose/obnoxious.
I accept these propositions and their implication.
For the first example, I don't see how it has anything to do with C#.
For the second example, I assume it works like Matlab? By "discard" it just suspends the print, but if you use something like `a = 1;` it still assign 1 to a? If so, I think it's perfect fine for notebook-like interactive shell.
Quite often the rendering of an object would take so much time, that the interactive shell would hang, or so much space that it's not worth to show it.