
How many keywords I can fit into a single C# expression? - abbeyj
https://www.tabsoverspaces.com/233768-how-many-keywords-i-can-fit-into-a-single-csharp-expression
======
bluepnume
I got to 14 (now 15!) in javascript so far:

    
    
        async function* foo() {
            return yield delete void await null in this instanceof typeof new class extends async function () {} {}
        }
    

(not sure whether the code needs to successfully run? But it at least parses)

What's really fun is seeing how babel transpiled the above function:

[https://babeljs.io/repl/#?babili=false&browsers=&build=&buil...](https://babeljs.io/repl/#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=IYZwngdgxgBAZgV2gFwJYHsICp7vQCgEoYBvAKBkqsoCcBTZBGiGMVOgGwBMYvOG6MYAHdgqZDAgIOHGKhbIAFqhByIIZMGh10cGMjAAHHXoh1hMKB1Cq6AD2R0IXVaEixEKDCyKkAvv4UVH5AA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=false&presets=es2015%2Ces2016%2Ces2017%2Creact%2Cstage-0%2Cstage-1%2Cstage-2%2Cstage-3%2Ces2015-loose&prettier=false&targets=&version=6.26.0&envVersion=)

EDIT: here's an explanation [https://medium.com/@bluepnume/how-many-keywords-
can-you-plac...](https://medium.com/@bluepnume/how-many-keywords-can-you-
place-in-a-row-in-javascript-fa9046b4c1ff)

~~~
deathanatos
Well, you are correct that that parses. But _how_? I'm with it right up to
around "new class extends"; `new` I'm okay with, but doesn't `new` require a
symbol (the name of the type to allocate) after it? I tried parsing "class
extends async function" as an expression that boiled down to a type that we
could new, but I get lost at "class extends" — don't we need a class name
there? (Also, I didn't think `class` could be used in an expression…)

~~~
chrismorgan
Here it is with line breaks and as many extra legal parens as I can add
without redundancy, showing all the unary and binary operators involved:

    
    
      return (
        yield (
          delete (
            void (
              await (
                (null) in (
                  (this) instanceof (
                    typeof (
                      new (
                        class extends (
                          async function () {}
                        ) {}
                      )()
                    )
                  )
                )
              )
            )
          )
        )
      )
    

`class extends X {}` is an anonymous class extending X. Syntactically, X is an
arbitrary expression, which `async function () {}` is. At runtime, X must be a
constructor on pain of TypeError, and an async function isn’t; this could be
considered grounds for disqualification, as under no circumstances will it
execute without exceptions.

`new` does not require an identifier or path to follow it: it can be an
expression. So `new (foo)` is fine. It’s just kinda greedy as regards an open
paren of a function call: `new foo.bar(baz)(quux)` is equivalent to `(new
(foo.bar)(baz))(quux)` rather than any alternative grouping. Remember also
that `new foo` is legal, and equivalent to `new foo()`.

~~~
deathanatos
TIL class expressions and anonymous classes are a thing that exists in
JavaScript.

~~~
chrismorgan
It’s the same thing as function expressions and anonymous functions, but with
classes instead of functions.

------
hekul
> fit into a single C# expression

Mostly unrelated - but this reminds me of a project I did years ago to put an
entire raytracer in a single C# LINQ expression.

[https://github.com/lukehoban/LINQ-
raytracer/blob/master/READ...](https://github.com/lukehoban/LINQ-
raytracer/blob/master/README.md)

~~~
ccvannorman
This is awesome!

I have been warned that LINQ is often inefficient compared to raw C# with the
same functionality -- but I have my doubts as the MS C#/.net put quite a lot
into LINQ efficiency under the hood. Did you test this against any raytracing
setups that don't use LINQ?

~~~
wenc
LINQ isn't really inefficient as such -- that's not really how I would put it.
When used correctly, the performance is pretty good and it buys you elegance
and a massive amount of expressive power with relatively little performance
tradeoff. I use LINQ everywhere in my code and the maintainability gains are
palpable.

It does have overhead, but it shouldn't be avoided for that reason unless
you're really trying to squeeze performance gains out of your code. Eric
Lippert, the designer of LINQ, has this to say [1].

[1] [https://stackoverflow.com/questions/3769989/should-linq-
be-a...](https://stackoverflow.com/questions/3769989/should-linq-be-avoided-
because-its-slow/3770194#3770194)

~~~
cm2187
Though my understanding is that if you do something like this:

var a = new double[10000];

var b = a.Select(x=>x*x).ToArray();

I'd expect LINQ to create a List or a similar structure when executing the
select, then copy the output to the final array. If you were operating on two
arrays directly, I would expect less memory allocations (unless these things
get optimized away).

~~~
thecopy
No. The Select-statement is not evaluated until necessary, i.e. until
ToArray() is called.

~~~
0xffff2
While your comment is true, I'm not sure how it's relevant. `ToArray` is still
operating on the `IEnumerable` produced by Select, which doesn't tell you that
the Select is itself operating on an array.

The CLR does do some type checking to try to propagate the size if it can, but
that's by no means guaranteed.

Edit: In skimming the source available on sourceof.net, it looks to me like
the array doesn't actually get propagated far enough, so the construct in GP
will in fact incur several unnecessary array copies.

~~~
munchbunny
I'm curious what you saw. My understanding was that it's mostly generators,
i.e. it's done through yields, not extra allocated arrays.

~~~
0xffff2
It's `ToArray` itself that's the problem. Since it doesn't know how big the
array will be in the first place, it has to allocate a small array, start
iterating, copy everything into a bigger array each time it runs out of space,
and finally allocate an array of exactly the right size and copy everything
into that.

You can see the `ToArray` implementation at [0], which defers the
implementation to [1]. The implementation checks for ICollection to get the
exact size, but the type [2] that `Select` returns doesn't implement
ICollection, so `ToArray` has to fall back to the less efficient algorithm.

[0]
[https://referencesource.microsoft.com/#System.Core/System/Li...](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,783a052330e7d48d)

[1]
[https://referencesource.microsoft.com/#System.Core/System/Li...](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,ed118118b642d9d4)

[2]
[https://referencesource.microsoft.com/#System.Core/System/Li...](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,cffd4efbf86815cb)

~~~
cm2187
That's more or less what I had in mind without being certain of the details.

Was trying to open your links but I am served an expired tls certificate from
Microsoft!

~~~
0xffff2
Oddly enough it seems that the certificate just happened to expire today in
between the posting of my first and second comment. I didn't mention it only
because I'm at work and couldn't be 100% certain that it's not my
organizations security service messing with things.

------
xtreak29
Not with keywords but this reminded me of Black Perl :
[https://en.m.wikipedia.org/wiki/Black_Perl](https://en.m.wikipedia.org/wiki/Black_Perl)

------
monocasa
Is there a formal grammar? You could probably plug that into a SAT solver and
not have to guess.

~~~
jonathanyc
How would you use a SAT solver? Do you have a cleverer way than generating an
expression in a breadth first search?

------
magnat
Here's one with 8 keywords:

    
    
        abstract class A
        {
            internal protected abstract ref int X();
        }
    
        class B : A
        {
            unsafe extern internal protected override sealed ref int X();
        }

------
lalaithion
Haskell:

    
    
        case x of _ -> True 
    

is a valid expression of type boolean for all values of `x`.

    
    
        if b then True else False
    

is a valid expression if `b` is a boolean.

These can be nested recursively infinitely, but that's not very interesting,
so let's move on.

    
    
        let x = z in y
    

this is another valid expression for any y and z values.

    
    
        do e
    

this is a valid expression if e is a type that implements the Monad typeclass.

Basically every other keyword is for type declarations or module importing,
and those both aren't expressions, and seem much harder.

So, my final expression is:

    
    
        let x = x in do case if let x = x in True then False else False of _ -> return False
    

where `in do case if let` is my longest expression.

~~~
victorNicollet
Given the rule:

    
    
      expr := 'if' expr 'then' expr 'else' expr
    

An expression may start with a sequence 'if if if if ...' of arbitrary length
(and will contain that same number of 'then' and 'else' keywords, although not
as a sequence).

~~~
FBT
A simpler variation of the same concept is to use the fact that for every
expression `expr`, `do expr` is a valid expression, allowing you to start a
sequence with any number of `do`s.

But the challenge as I see it is to stack as many _distinct_ keywords in a
row. In javascript you can do the same thing with `await`: for every
expression `expr`, `await expr` is a valid expression, so you can start with a
sequence of any number of `await`s. That's great, but ultimately not a
challenge: the real puzzle is in stacking as many _unique_ keywords as
possible.

------
miguelrochefort
I can do 14 using parenthesis:

    
    
        case default(string) when await unchecked(nameof(value) as dynamic) is checked(sizeof(decimal)):

~~~
lostmsu
He did not even try too hard.

You can, obviously, add all of the standard type names and both true and false
to your expression easily via ||. Add an out and ref too.

Also, if you put a local function into an async lambda (not sure if that's
possible), you can add all the control flow operators, and using too.

The only things you won't be able to fit there, IMHO, are type keywords
(class, interface, etc), namespace, visibility modifiers, static, abstract,
virtual, override and operator.

Actually, it might be easier to tell which keywords you can't fit into an
expression.

~~~
jameshart
The challenge the post sets is to line up as many keywords as possible in
sequence, without separating commas, semicolons, parens, braces, symbolic
operators, etc.

So yes, of course if you ignore the constraints on the problem it becomes much
easier.

~~~
miguelrochefort
The challenge description has an example using parentheses, and doesn't
explicitly prohibit them:

> yield return sizeof(...)

~~~
Dylan16807
The part submitted for scoring is just "yield return sizeof", that's why he
says it scores 3. The keywords can be surrounded with more code to make them
compile, but none of it counts for the puzzle.

------
userbinator
As someone who briefly used C# back in the days of .NET 2.0 (and found it was
not much different from Java at the time), it's surprising how much has been
added to the language, and my main question is thus (assuming this is a valid
expression) "what does it mean?" Especially the non-constant in a switch case.

~~~
mattnewport
C# 7 introduced pattern matching in a number of contexts, including switch
statements. It's not as elegant or powerful as F# or some other functional
languages but still quite useful.

------
ygra
By my count that's only six keywords as _case_ is a keyword, but not part of
an expression.

------
christofosho
Giving Python a try was fun~

>>> not None and True or [False for _ in dict() if print(lambda: int is str)]

~~~
deathanatos
But [] isn't really a keyword, and `dict`, `print`, `int` and `str` (and `_`)
are definitely not keywords (they're just function/type names/symbols; they
can be shadowed, for example).

~~~
re
"How many keywords can I fit into a single expression" and "How many 'blue
words' can I put consecutively with only spaces between them" (as described in
the post) are two pretty different challenges. With only distinct keywords
only separated by spaces, best I could do was 10:

    
    
        def _():
          yield from None if not True is False else lambda :_

~~~
uryga
up to 11! (parses on 3.7.2)

    
    
      async def _():
        async with await None if not True is False else lambda: _:
          pass
    

it's a shame that Python has so few expression keywords... `yield` can be used
as an expression, but it seems to need parentheses :(

------
ramenmeal
C# is a mess. I'd love a C# light.

~~~
nicoburns
Really? C# seems very balanced and coherent to me. What seems messy to you?

~~~
klodolph
The messy part of C# is the way it does function overloading. (Speaking as
someone who likes C#.)

~~~
lsadam0
Why do you think function overloading is messy?

~~~
klodolph
I’m not making any claims about function overloading in general. Only C#.

It interacts in surprisingly complicated ways with a surprising number of
other parts of the language. It causes problems with prospective new features.
IIRC there have been a number of bugs in the spec and in the compiler wrt
overloading. Given the overall high level of expertise of people working on
the spec I would say it’s a bit of a minefield, compared to the rest of the
language.

Edit: Found a Q&A by John Skeet,
[https://youtu.be/8UvTdobOiJk?t=1292](https://youtu.be/8UvTdobOiJk?t=1292)

“Overload resolution is the nexus that every feature ends up contributing to.
[…] All of these things make overload resolution and type interface really
really really complex and it’s always a bit of the C# specification that I
found hardest to understand […] and it turns out so did the spec designers,
because they got it wrong, and we’ve been trying to fix it.”

~~~
OskarS
It might be tricky from a standards point of view, but as a user of the
language, I don't think I've ever had an issue with how it handles overload
resolution. Every once in a while you get an "call is ambiguous" compiler
error, but I've always found the "ambiguity" to be reasonable and easy enough
to fix (by making generics explicit or using an explicit cast). It's also a
very minor bummer that you can't overload on return type, but that is A LOT to
ask of a language.

Generally speaking, I've found C# to be a very pleasant language to work in,
by far my favorite in that particular "lane" of computer languages (i.e.
annoyingly object-oriented enterprise languages, like C#, Java or C++).

