Illegal prefix operators sometimes compile fine, but give confusing errors when applied
When trying to experimentally determine what operators can be overloaded as prefix operators (the docs are sorely lacking and even misinforming, as this SO question shows), I stumbled upon the following weird behavior:
let (?%) x = x + 1
let test = ?% 2
Two things to note:
- In Visual Studio it shows as a warning (orange curly)
- When compiled, or when hovering over it, it throws the error "FS0010: Unexpected infix operator in binding" (which itself can be resolved by using parentheses around the operator)
According to the docs and the spec, at least what I make of it, a prefix operator must start with a !, a ~, a + or a -. The above shows that a prefix operator can also start with a ?, but this seems a bug (I mean, I think it should be disallowed), because:
let (?!) x = x + 1
will fail to compile, throwing the (expected) error "FS1208: Invalid operator definition. Prefix operator definitions must use a valid prefix operator name.".
I fully realize that I may not understand the docs or the spec well enough to know what prefix operators are illegal or not, but I think the above shows that there is at least some ambiguity to how the compiler treats the rules, and it shows that the docs seem incomplete.
To illustrate the latter, MSDN F# operator precedence shows +op and -op as valid, but:
let (+^) a = a // compiles
let test = +^ 2 // throws FS1208: Invalid prefix operator
Again, this can be resolved by using parentheses, but here we see a different error in a similar situation, besides, this situation uses an example from the docs (and it seems to me this is allowed by the spec as well), but fails to compile, unless you use parentheses (though I fail to see ambiguity issues here, nor precedence rule issues).
Finally, the last (different, again) error I noticed is with:
let (+.) a = a // compiles
let test = +. 2 // throws FS0001: The type 'int' does not support the operator '~+.'
// and throws FS0043: The type 'int' does not support the operator '~+.'
again solvable by using parentheses which, I think, are again not needed.
These behaviors mostly look potentially okay to me, although I agree that there's some weirdness with how '?' can appear as part of operators that should be cleaned up/documented. I assume these operators were allowed to enable the Linq.NullableOperators module to be written in the F# 3.0 timeframe.
In the first case (with ?%), you're defining an infix operator (c.f. Linq.NullableOperators.(?%)), but because of the actual definition it can never be used as an infix operator (since after applying it to the left operand you'll get an int which can't be applied to the right operand). So it's by design that you can't use it as a prefix operator.
The second case (with ?!) is the only one that seems perhaps wrong: somehow the '?' part of the operator isn't taken into account when determining whether an operator is a prefix or infix operator, so the compiler treats (?!) as a potential prefix operator definition, but then disallows it because it doesn't fit the pattern of valid prefix names. However, it looks like you can create certain prefix operators containing '?', like ~?-, but not others, like ~?-., so the logic here should be double checked and made consistent and documented.
The third behavior looks okay to me; +^ is not a valid prefix operator but you're using it as one in the second line; by contrast using it as an infix operator works just fine (although the definition of the operator doesn't lend itself well to this): (fun x -> x + 1) +^ 2.
Finally, +. can be used as either a prefix or infix operator (presumably for compatibility with OCaml); however, to define the prefix version you need to use the name (~+.) even though the ~ is dropped when using the operator. Therefore the first line in your second example is a red herring - you're defining an infix +. operator but using the prefix one on the second line. Without the first line you'll see exactly the same message on the second line, and the error message itself indicates what you need to do to get it to work (define the operator using (~+.)).