9
\$\begingroup\$

Edit: I've broken the proposal out into an answer, but left the examples where they are. Please do not consider a vote for this question to constitute support for the proposal

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 (https://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 (https://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: https://codegolf.meta.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)); // returns 2
Console.WriteLine(((Func<string, string>)(a=>a+a))("1")); returns "11"

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

Note: strictly the behaviour of a self-contained method is still open to external influence, even if it compiles fine in minimal boiler plate (unsafe code can ruin your day, or aliasing/replacing types for example) but requiring such behaviour would be obvious and blatantly not in the spirit of things

Summary

There can be no clear-cut objective answer to "what is a function", because it would have to be per-language and such.

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

How would people like to deal with this concern. I'm interested in responses that answer the following:

  • How do we define a valid submission (must cover the above concerns, either resolving or presenting a case for their insignificance) in C# (though comments regarding other languages with similar concerns are welcome)?

  • How and to what extent do we enfore any new rulings?

  • Do we except existing answers?

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

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: https://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: https://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: classic 'test' code for Example A, and what I would consider a good (i.e. by my proposal 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: classic 'test' code for Example B, and what I would consider a good (i.e. by my proposal 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 method

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

        /* calling code */
    }
}

Example G: what I would consider a good (i.e. by my proposal valid) version of Example A which requires direct calling

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

Example H: boiler plate for calling an anonymous well-typed method (e.g. Example G)

// taken to be in a method
var result = (/*submission here*/)(/*arguments*/);

(puts the result into a variable so it can used for anything, it doesn't matter what - note that the compiler knows the type, hence var works here, and that the extra parentheses save having to include them in the submission)

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.

\$\endgroup\$
8
  • \$\begingroup\$ Re your note at the top, yes, it's pretty much the same situation in Java. \$\endgroup\$
    – Geobits
    Commented Dec 19, 2016 at 17:47
  • \$\begingroup\$ Also related. \$\endgroup\$ Commented Dec 19, 2016 at 17:57
  • \$\begingroup\$ Putting your proposal in an answer might be a good idea. Regardless of that, for the case where you have an unnamed function with a cast, I'd be fine using that without the outer parentheses. In general the rule isn't "functions must be callable by name or by code(args)" but "function submissions must either leave a named function defined or must be expressions that evaluate to a function". In the latter case, whether that expression is directly callable or not due to precedence concerns is usually ignored in many languages. \$\endgroup\$
    – Martin Ender Mod
    Commented Dec 19, 2016 at 20:15
  • \$\begingroup\$ @MartinEnder that seems reasonable, probably a misunderstanding on my part, though requiring it to be callable makes it easy to validate (though we can just add the parentheses into the assumed boilerplate and have the best of both worlds). I'll have a bash at pulling the proposal out without completely butchering the question or repeating myself too much (I didn't do so originally because the proposal and my argument for why this matters are kind of the same thing and I didn't want relevant concerns overlooked). \$\endgroup\$ Commented Dec 19, 2016 at 20:22
  • \$\begingroup\$ "My function, when called with the right input types, produces the correct output" - how can you call that an invalid submission? If you want to assign it to a variable, you have to specify the input types - that doesn't make the lambda any less valid. I'm pretty sure all of the Python solutions would break if I put in the wrong types - however, given the right types, the function works. Similarly, given the right input types, these lambdas work, and are therefore valid answers. \$\endgroup\$
    – FlipTack
    Commented Jan 6, 2017 at 21:34
  • \$\begingroup\$ Also, why are you just trying to add bytes to the already most verbose languages? \$\endgroup\$
    – FlipTack
    Commented Jan 6, 2017 at 21:36
  • \$\begingroup\$ @FlipTack that question is just what I'm trying to answer above... the gist being that C# is statically typed, and without types the code becomes meaningless. Comparable Python code wouldn't be meaningless, because the type system is different. If there is a particular part that doesn't make sense, please do flag it up; your comment is a bit vague for me to answer sensibly. I genuinely couldn't care less about the byte counts involved, this is about correctness and what constitutes a valid submission (and speaking as an ardent C#er, I think you'll find that Java is the most verbose language!) \$\endgroup\$ Commented Jan 7, 2017 at 0:10
  • \$\begingroup\$ @FlipTack (alternatively, if you think that I'm missing the point completely, feely free to post an answer with a different proposal. I'm keen to have a consensus we can point to. I don't really care what it is (as much as I think my suggestion makes sense). Ideally the proposal will be somewhat C# specific, but I can edit details in as necessary if I think I get where you are coming from). \$\endgroup\$ Commented Jan 7, 2017 at 0:16

2 Answers 2

1
\$\begingroup\$

Submission Validity

My proposal is 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 (or even properties), because both must be named, and as such must be well typed. To be callable, the code must also be well typed, for example:

(System.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. MartinEnder has made a case for not requiring the outer parenthese and they have as such been accouted for. This code fullfills the requirement of being unambiguous, and a well-defined boiler plate has been provided (Example H)

Consult Examples C, D, and G for what I would consider valid solutions based on real-world examples.

Note that it could well be better to write a classic named function given the constrints, or use the new C#6 method syntax. This will essentially render most uses of lambdas as the top-level code element redundant (I can't think of any exceptions). The a=>a+a example above can be written thus in C# 6:

int K(int a)=>a+a;

I don't see this as a problem, it is merely the consequence of having a well defined ruling on the matter which ensures the portability and fair comparison of C# answers.

I should like to bring attention to the fact that these rules pretty much gaurentee that any piece of C# supplied on PPCG can be pasted directly into a REPL and be expected to work.

Enforcement

I would suggest that people should not upvote new invalid answers (however we define them), and leave comments to inform them of their dubious validity (and perhaps downvotes for people that should know better). I don't have a suggestion for how to deal with the abundance of answer that would be invalidated by such a ruling but to except them, though this will leave examples of 'bad practice' for new users to follow. I think this rulling is general enough to apply to other such decisions for other languages and platforms.

Note: I will not post (many) other answers which disagree with this assessment, because the space of answers is large. Pleased down-vote, comment, and provide alternative suggestions if you do not agree.

\$\endgroup\$
2
  • \$\begingroup\$ It seems like the anonymous function is not interessting for golf. Naming this function takes 29 bytes System.Func<int,int>f=a=>a+a;. Making it anonymous takes 30 bytes (System.Func<int,int>)(a=>a+a) \$\endgroup\$
    – Hedi
    Commented Dec 25, 2016 at 10:15
  • \$\begingroup\$ @Hedi indeed, and there are even shorter ways of naming it in C# 6. As I say, I don't see this as an issue, it is just a consequence of a ruling which I think makes sense. The reason I go on about them so much is because there are 'anonymous delegate' answers of the form a=>a+a which I am arguing should be invalid. \$\endgroup\$ Commented Dec 26, 2016 at 11:27
-1
\$\begingroup\$

Given the limited involvement here and mixed voting, I've opted to post another proposal based on 'typed lambda parameters' - hopefully this will be more agreeable because it doesn't increase byte counts as much

Submission Validity

Along with being directly callable (examples G and H) or assignable, a submission may be a lambda with explicitly typed input parameters. An example of this would be

(int a, int b) => a + b;

While this is neither directly callable nor assignable, and strictly has no type, I believe it is near-enough unambiguous as far as the C# compiler is concerned.

Testing such a submission would involve typing it, however there is only a limited subset of types that it can be assigned to, and crucially the input parameters of the delegate must match exactly, but the return value need only be implicitly convertible. This means that the behaviour of the lambda is well defined up until it returns, at which point it could potentially be cast to a number of things. This example hopefully shows why this is not an ideal policy:

 Func<int, float> q = (int a) => a;
 bool b = q(int.MaxValue - 9) == q(int.MaxValue) // true

However, I would argue that here C# is just wrong, and that the implicit cast from int to float should not be allowed, but it is. Note that my complaint here is not to do with allowing assignment to use implicit casts, but that ints in C# are allowed to be implicitly casted to floats, without even a warning. double would be fine, because it round trips to int without changing (I believe)

To make the rules absolutely clear, we defer the decision of the return type to the C# compiler. This can always be determined by assigning the return value to a var local, and then interrogating this type. For example, the following would be a valid submission:

(int a, float b) => a * b;

Which we can rewrite as:

(int a, float b) => { var l = a * b; return l; };

Determining the type of l (it's a float) tells us that we must consider this to be cast or assigned to something returning a float, for example:

Func<int, float, float> w = (int a, float b) => a * b;

This means that if the submission (for whatever reason) requires a cast then it must specify it in the return clause. To take a stupid example, if the challenge was "turn a .NET System.Int32 into a .NET System.Float32", then (int a) => a would not be valid, because the rules require we type it as returning an Int32. It would have to be (int a) => (float)a

I'll attach the usual disclaimer that I'm not just trying to make C# less competitive, I'm trying to make it genuinely competitive by providing rules for the competition. Again, if you disagree, please down vote (rep doesn't matter on meta), comment, and provide another answer if you think you have a better idea!

Example I: 'Typed Lambda'

(int a) => a + a

This proposal would consider anything of this form a valid submission. All input arguments must be explicit.

Example J: Example 'valid' test code for I

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

Enforcement

I would suggest that people should not upvote new invalid answers (however we define them), and leave comments to inform them of their dubious validity (and perhaps downvotes for people that should know better). I don't have a suggestion for how to deal with the abundance of answer that would be invalidated by such a ruling but to except them, though this will leave examples of 'bad practice' for new users to follow.

\$\endgroup\$
3
  • \$\begingroup\$ typo '(int a, float bb)' \$\endgroup\$ Commented Jan 8, 2017 at 21:07
  • \$\begingroup\$ @TrojanByAccident thanks, that's what I get for using real code as a scratch pad \$\endgroup\$ Commented Jan 8, 2017 at 21:09
  • \$\begingroup\$ Anytime :P Nice answer, btw \$\endgroup\$ Commented Jan 8, 2017 at 21:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .