Skip to main content
1 of 11
VisualMelon
  • 3.9k
  • 10
  • 10

C# Lambdas as Answers to questions asking for a Function

Apologies in advance, this is long, and may read somewhat like a rant, but I've been holding off asking about this for ages (and reduced my PPCG activity as a result of the increased prevalence of certain classes of answers). I'm sure many will expect that this is a dupe of something more general (another reason I've been slow to write this up) but I can't find any fitting questions or satisfactory answers.

Note: this is a problem common to multiple languages, I'm asking about C# specifically to avoid this being unanswerable because it's too broad, and because I know and love (golfing) C#. From what Java I know, this is probably directly applicable. It's also a 'common case' of what I consider a wider broader concern.

Note: the answer here expresses some of my sentiment, but not quite all of it.

Today I was looking at this question: Output integers in negative order, increase the maximum integer everytime

There are 2 C# answers, and frankly I don't like either of them, but I gave one of them a hard time and not the other, because I think that more people would agree with me that it is perhaps not so much in the spirit of things. I've neither up-voted nor down-voted either, because I'm not happy with this issue. Having thought about it a little more, I've concluded that they are both as bad as each other, and as such we seem to be holding a double standard at the moment.

One answer (http://codegolf.stackexchange.com/a/103672/26981) is of a form which we see all the time in C# answer these days, it's the code for an anonymous lambda.

The other (http://codegolf.stackexchange.com/a/103692/26981) is also a form of anonymous lambda, only this one is recursive, and as such the lambda must be defined and assigned before it can be assigned.

Neither of these compile by themselves. To get them to run, you have to wrap them up somehow. Any modern C#er will know how to do this, but while the first can remain anonymous, the other has to have a name, and this is why it feels a bit odd allowing it, when we are assuming some declaration exists for it (even if the name is indicated in the code submission). Instead of doing this (as for the first answer):

Console.WriteLine(((Func<int, int>)(/*code here*/))(1));

We have to do this (because it's recursive) for the second:

Func<int,int>z=null;/*code here (starting with z=)*/;Console.WriteLine(z(1));

The above assuming a using System; directive in scope

While not long ago I would have let the first slip, but complain about the second (meta seems to agree: http://meta.codegolf.stackexchange.com/a/10361/26981), I don't now think there is a meaningful difference. My complaint falls into two parts:

Complaint Half A

The code provided does not have familiar and well defined boilerplate to be packed around it. All over PPCG we see people saying that it's OK for C# and Java to omit the (required for csc) class definitions and just provide a function, because we all know what it is, and it just adds bytes (I would respond "who cares then?" but that's just me), but that is not the case here, because these lambdas lack type info, and as such there is a very large space of boiler plate to inspect. This is my "I think this is the spirit of PPCG" concern.

Complaint Half B

Even if we say "well, a human can work out what types it needs to be, so it's really just boiler plate" then we still have a big problem, something along the lines of C# is statically typed and takes types into consideration during compilation, a consequence of which is that there can be multiple types we can assign a delegate to which produce different behaviour. Consider a challenge "double an integer", and the answer (6 bytes: a=>a+a):

Console.WriteLine(((Func<int, int>)(a=>a+a))(1));
Console.WriteLine(((Func<string, string>)(a=>a+a))("1"));

If the question spec was strong enough, and specified that input must be a numerically typed integer, then we could say "well, the first one is correct, the second one is wrong, but this never happens because 'numerically typed' doesn't really mean anything, and certainly doesn't make sense in a whole host of languages. It's pretty standard to see a question with answers that take input from argv (i.e. a string) and others which take it as input to a method/function/block_of_code/callable_expression/whatever which either demands it be of a 'numeric type', or assumes it be of a 'numeric type' (which is fine in Python (for example) where they don't have the luxury of a static type system).

Given that the spec can't tell us the type, and that the code can't tell us the type, this should not be a valid submission, because it is ambiguous beyond just needing boilerplate. This doesn't preclude allowing functions, because a function definition is self-contained, and either it can be stuffed into a class (where it will compile, carry unambiguous behaviour, and can be called), or it cannot (it won't compile, e.g. name conflicts).

Summary and Proposal

There can be no clear-cut objective answer to "what is a function", because it would have to be per-language and such, but in C# at least, it is my suggestion that a function must be callable (this is generally agreed on meta) either 'directly' (e.g. code(arg1, arg2 ...) or by name (e.g. methodname(arg1, arg2 ...)) where the definition could be stuffed into a non-static class or method and called from within that class (assuming no name clashes).

Note that I make no distinction between 'typical' methods and 'anonymous' methods here, because both must be named, and as such both must be well typed. To be callable, the code must also be well typed, for example:

((Func<int, int>)(a=>a+a))

I would consider the outer parentheses to be necessary, and as such count towards the byte count, because while without them this can be assigned (var q=(Func<int, int>)(a=>a+a);) it is not directly callable, or callable by any name.

I'll ignore here the issues of functions needing using directives, and library references, etc.

Note that I'm not just being miserable here and trying to make C# harder to golf, I think that these are real issues which must be addressed, because they give some C# answers an unfair advantage if we make such allowances, and reduces opportunities for golfing (who cares if all C# answers are a minimum of 26 bytes? they provide utility and ignoring them is irrational).

The actual question...

Would people agree with this proposal (if not then why not, do you have a better idea), and if so, how should we enforce it (commenting, down-voting, withholding up-votes, etc.).

I will (attempt to) move the proposal into an answer if people think this would make more sense.

Currently the situation feels like people just vote for stuff they approve of, and hopefully bring up concerns they have with stuff they don't approve of (not necessarily down voting when it's unclear whether the community agrees). An answer to this question could well be "we can instinctively judge this, keep on being 'sensible' and it will be ok", but I think this should be a fall back as opposed to a first-class suggestion.

I would like for this to be somewhere we can point C# users, but certainly don't object to any relation to other languages with similar concerns (I'm sure this could be asked wrote for Java).

Examples (for reference in any discussion, feel free to add more...)

Example A: something which I think the community currently would think is fine (source: http://codegolf.stackexchange.com/a/103672/26981)

n=>{var s="";for(int i=0,j;i<n;)for(j=++i;j>0;)s+=j--;return s;}

Example B: something which I think the community currently isn't sure about, but would err on the side of rejection (source: http://codegolf.stackexchange.com/a/103692/26981)

f=n=>{int x=n;var r="";while(n>0)r+=n--;return x>0?f(x-1)+r:"";};

Example C: what I would consider a good (i.e. by my suggestion valid) version of Example A (note semi-colon at end also)

System.Func<int,string>q=n=>{var s="";for(int i=0,j;i<n;)for(j=++i;j>0;)s+=j--;return s;};

Example D: what I would consider a good (i.e. by my suggestion valid) version of Example B (note semi-colon at end also)

System.Func<int,string>f=null;f=n=>{int x=n;var r="";while(n>0)r+=n--;return x>0?f(x-1)+r:"";};

Example E: boiler plate we can assume for something going into a class

/*usings here perhaps?*/

class LONGCLASSNAMETHATWONTCLASH
{
    /*submission here*/
    
    /*calling code (in a method or w/e)*/
}

Example F: boiler plate we can assume for something going into a class

class ANOTHERLONGCLASSNAMETHATWONTCLASH
{
    void LONGMETHODNAMETHATWONTCLASH()
    {
        /*submission code*/
        
        /* calling code */
    }
}

Note: this is part of a broader concern about submitting code which simply doesn't compile. Generally, it seems wrong to have to put the code provided in any sort of 'environment' to get it running: when does this extra stuff stop being the 'environment' and start being code? We don't count the code required to call the compiler, but why not? C# is only meaningful if you compile it with the right compiler, etc.

Note: this is something that has been discussed around a lot, but I can't find a satisfactory consensus. It's also something that most people seem not to care about too much, which is fine by me, but when I'm voting, I like to vote for answer which are creative, show a serious attempt at golfing (I can't just pull 5 bytes out of them without thinking), and comply with the rules: if the rules aren't defined, then I struggle to up-vote stuff. I do generally up-vote (and down-vote) C#, Java, PoSh, and even F# answers more than others, because I know them all well enough that I could have a serious go at golfing them if I could be bothered.

VisualMelon
  • 3.9k
  • 10
  • 10