
It’s strange what people put up with in C# - dustinmoris
https://gist.github.com/dsyme/32de0d1bb0799ca438477c34205c3531
======
manigandham
This guy is the designer of F# complaining that C# is not more like F#.

" _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._"

[https://www.microsoft.com/en-
us/research/people/dsyme/](https://www.microsoft.com/en-
us/research/people/dsyme/)

~~~
algorithmsRcool
> This guy is the designer of F# complaining that C# is not more like F#.

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...](https://blog.ploeh.dk/2015/04/15/c-will-eventually-get-all-f-
features-right/)

~~~
manigandham
Sure, mostly because the C# team is great and has been able to pick and choose
the best features among several languages and ecosystems. We now have most of
those features listed in the article since 2015 and the pace is only getting
faster with point releases. Even the comment saying non-nullable reference
types would never appear has already happened a year ago.

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.

------
jeroenhd
Very strange take in my opinion. I haven't used C# in a while but I guess the
compiler doesn't warn when you discard a return value? I suppose that's kind
of annoying when debugging. I don't know about many languages that do this by
default though.

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.

~~~
goto11
The compiler does warn when you discard a value, but I assume a C# notebook is
something like Jupyter? It is probably a special case in that environment.

There is Comparer.Create convenience method which creates an IComparer
instance from a lambda. But yeah it is a workaround.

~~~
np_tedious
Sounds fine to me. Unused valuable will get you some squiggle warnings in the
editor, but not in a notebook.

As far as "language gotcha" articles go this is pretty weak IMO

~~~
dcanelhas
The semicolon at the end of a statement is possibly matlab-inspired.

------
yyyk
There are real advanced issues here, not just syntax issues. I'd summarize the
first four items like this:

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.

~~~
rndgermandude
(1) I discussed here and I do not even know what's going on there:
[https://news.ycombinator.com/item?id=24168274](https://news.ycombinator.com/item?id=24168274)

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;
        }

~~~
yyyk
1) Immutable collections is a bit of a pain in C# right now. No LINQ To()
methods, some nugets don't actually work with them, constructor methods can be
nicer...

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.

~~~
rndgermandude
I thought about creating a small library that would expose a static
IComparer<T> ToComparer<T>(this Func<T, T, int>) extension method (and a
similar one for IEqualityComparer<T>). But I so far just was too lazy. If
somebody from the C# API team reads this tho.... :P

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.

------
codeulike
The complaint about 'implicit information loss' is a bit weird, the code calls
a function/method that may do other things, the compiler doesn't know whether
you intend to ignore what's returned or not. To return 'true' when you meant
to return the result of the function call on the previous line is just a bug.
There are going to be bugs.

~~~
frabert
C++ introduced the [[nodiscard]] attribute for this exact use case. A C#
[NoDiscard] attribute would do the same thing with the help of a Roslyn
analyzer I guess.

~~~
goto11
C# already by default warns on discarded return values.

~~~
algorithmsRcool
No it does not, unless the discarded value is a Task in an async method. (or
sometimes an IDisposable depending on what analyzers you have running)

------
apalmer
Not sure about this take. Every language doesn't have to have every feature of
every other language. Simplicity is in itself a feature.

C# at this point is already probably 'larger' as far as feature set goes other
mainstream languages.

~~~
ddek
I’m not overly enthusiastic about most of his suggestions. I thin C# has, or
is gaining, more appropriate alternatives for all of these.

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.

~~~
walleeee
> 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.

Why not have the ctor accept a single options object? At least if
initialization is your only reason for allowing mutable props.

------
breakingcups
Alternative title: "It's strange what some people expect of C#"

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.

~~~
algorithmsRcool
C# is pretty great, I use it everyday. But if you've even spent significant
time in F#, you return to C# land asking some of the same questions Don does.

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.

------
Someone1234
"Implicitly discarding information" is just outright wrong.

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).

~~~
Smaug123
F# solves this by making it a compile error to fail to consume an expression
that returns anything other than unit. If you really must do this, you
normally use `ignore : 'a -> unit`. It's unidiomatic F# to structure your APIs
in such a way that you will routinely throw away the information you're given.

~~~
Gibbon1
I think really what people want are strict and sleazy compile options. Or
better the ability to tell the compiler that a return value is an error. Or
not. If it's not an error and it gets tossed who cares really. Sometimes you
don't care if every i is dotted and every t crossed.

------
coredev_
I created an account just to comment on this.

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

~~~
jarym
Have to agree! I mainly code Kotlin at the moment, however I still love C# and
actually F# too.

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.

------
soundmask
I very much hope no C# PMs are reading this and note ideas. C# is a fine
language, but it's already packed with too many features and ideas from too
many other languages. No need to make even bigger.

~~~
dustinmoris
C# PMs are already implementing everything from F#. Everything you see there
will be eventually C# as well. There's no doubt.

------
moomin
1) Yeah.

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.

------
nottorp
I read that as "i'm complaining that C# isn't a functional language" ?

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?

~~~
xupybd
You can but the business case for introducing F# can be tricky. It's a better
developer experience but due to the difficulty (or perceived difficulty) in
hiring F# developers it's seen as a liability.

~~~
oaiey
And to add: the existing imperative/oo programming workforce is of no use.

------
Semaphor
Does VS not have something like "return value of pure method not used"?
Because JetBrains ReSharper/Rider gives that warning.

> in a C# notebook

Anyone has any idea what they mean here?

~~~
UglyToad
You can have C# Jupyter Notebooks but if I remember correctly the same
behaviour is displayed in the immediate window in Visual Studio when
debugging. Doesn't seem like a language problem as much as working in a REPL
environment issue.

[https://github.com/dotnet/interactive#notebooks-with-net-
cor...](https://github.com/dotnet/interactive#notebooks-with-net-core)

------
ufmace
I don't find any of these terribly impressive.

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?

------
thelazydogsback
He could try:

\- 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.)

~~~
g5becks
If C# had these features it would be hard not to use it.

------
mhh__
Given that I was told that C# is "productive", I found myself quite surprised
at the amount of boilerplate I was writing. Better than C++ but really not all
that great.

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)

------
oughton
It is strange how quickly I stopped reading after the first listed example.

Return boolean | string? Perhaps they're thinking of TypeScript :-D

~~~
smt88
I've written many thousands of lines of both TS and C#, and I've learned two
things about type systems as a result:

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.

~~~
rwallace
I'm curious, what do you find are the advantages of structural typing?

~~~
smt88
With nominal type systems, you have to worry about explicitly associating
types with one another.

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.

------
peacefulhat
> No language in the 21st Century should have implicit information loss. There
> are likely C# analyzers to prevent this kind of bug, or you have other
> choices.

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".

~~~
ragnese
Yes, but he's talking about an equivalent of Rust's [must_use].

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.

------
ldd
This may be a bit of a tangent, but does anyone know how to implement a redux-
like pattern in C#?

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)

------
nurettin
you could use method chaining in order to create any dsl you want in any
language you want. Linq to objects is nice and you certainly have lambdas and
initialization syntax and both support duck typing so the post falls around
the I have no idea what I'm doing category.

------
frabert
The only one I've _really_ wanted are #2 and #3. #3 may be coming with new
versions thanks to "value classes" or something like that, but "anonymous
interface objects" are the only thing I actually miss from Java.

~~~
NicoJuicy
Use LinFu

    
    
        select new DynamicObject(new
     {
       A = value.A,
       B = value.C + "_" + value.D
     }).CreateDuck<DummyInterface>();

------
mrkeen
> C# already by default warns on discarded return values.

> 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.

------
1f60c
At first, I couldn't have agreed less with the author, but when I started to
write down my complaints, I realized we agreed on more than what we disagreed
about. This is why I read HN. Thanks!

------
fireattack
Could someone please elaborate his #1 a little bit (as I'm not familiar with
C#)?

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.

~~~
rco8786
Same question. It’s two entirely different pieces of code, even returning
different types. Is the author upset that C# allows side effect mutations?

~~~
strogonoff
Compiler didn't balk at unused return value. (Also by the sound of Format()
the function is typed as returning a string, but the author didn't raise that
issue.)

~~~
rco8786
I see... seems a rather subjective take (IMO)

------
kasajian
I put up with it because that's mostly where the work is that I want to work
on.

------
orthoxerox
#3 is coming in C#9 as records.

