Write custom operators like Swift #594
Replies: 58 comments
-
|
Beta Was this translation helpful? Give feedback.
-
@HamedFathi This feature interests me, I hope you don't mind if I try to answer @casperOne questions, so we might later achieve a proper proposal. I'm also considering being able to define operators both in instance classes (like C++) and static classes (like extension methods).
May I also ask: Would the already existing operators be accepted as static ones? |
Beta Was this translation helpful? Give feedback.
-
@Logerfo With your answer to 2, there's a problem; with extension methods, if there's a conflict, I can always use the fully qualified type name and method name to call the method. With operators, how do we call this if there's a conflict? I see no way to do that right now. Order of precedence should be the same as extension methods; if there's an operator on the target type then that takes precedence, otherwise, an "extension" operator (for lack of a better term) could be applied. How many characters can an operator have? I can see where this would get unwieldy/confusing with just one character. As for existing operators, they would have precedence, as they are defined on the target class already, by point 2. |
Beta Was this translation helpful? Give feedback.
-
It would be nice if operators could have named identifiers, eg.
|
Beta Was this translation helpful? Give feedback.
-
@YaakovDavis I'm suddenly a huge fan of named postfix operators.
|
Beta Was this translation helpful? Give feedback.
-
@HamedFathi An example declaration:
|
Beta Was this translation helpful? Give feedback.
-
With the names it sounds like the units of measure that exist in for example F# Here is an article of the feature in F#: https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/units-of-measure |
Beta Was this translation helpful? Give feedback.
-
Units of measure in F# are lost at compile time. Note though that "units" is just one use case of postfix operators. Conversions are another. |
Beta Was this translation helpful? Give feedback.
-
I fear that somebody is going to write a weired The idea of named postfix operators is actually quite good. But IMO they should be declared and used like extension methods (with // Extension operator on type int:
public shared explicit Temperature operator celsius(this int left)
=> new Temperature(left);
public shared explicit Temperature operator farenheit(this int left)
=> new Temperature((left - 32) / 1.8);
// Usage
Temperature w = 5.celsius;
Temperature x = 5.farenheit; |
Beta Was this translation helpful? Give feedback.
-
That would then come back to Extension Everything. There is still value in custom prefix and infix operators, and non-named postfix operators too. You can go completely crazy and write horrible code, but you could already do that... |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h of course you can already write completely crazy code and have op_add be a substraction operator 😁 But with the standard (symbolic) operators there it at least an expectation that I have on its operation. But with whatever operator is customly written I don't know the slightest meaning behind it, even when it looks okay, like e.g.
Yes, for the example above the extension then was a property |
Beta Was this translation helpful? Give feedback.
-
Ah yes, the twiddle-wakka operator (as paket calls it) Infix I would expect to mostly be used for making mathematical code look more like the formulae that it represents. Prefix... only thing I can think of off the top of my head would be currency literals, e.g.
|
Beta Was this translation helpful? Give feedback.
-
Custom operators, named or symbolic, can be used to build mini embedded-DSLs. Example 1 - a parser combinators (ala FParsec):
Example 2 - FRP:
Symbolic:
Example 3 - UI:
And with symbolic operators:
Those are just ad-hoc examples. Using such DSLs, one might create safer and easier codebases. |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h I don't agree that currency literals would be referenced with prefix operators. I see those as a postfix operator (just like Celsius and Fahrenheit). Of course that's a style thing, it's not here or there, but currency feels unnatural to me as a prefix. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
@orthoxerox Those restrictions aren't too harsh. The user can provide this information to the compiler. |
Beta Was this translation helpful? Give feedback.
-
I really find this argument frustrating. Yes, people can do stupid things. I could write an API that looks like this: public class A
{
public void B();
public void C();
public void D();
public void E();
} But I don't, because it's idiotic. This is akin to saying 'users can't be trusted'. Defining operators shouldn't be any different to defining functions. They're just functions with symbols for their names rather than words (although I'm totally for words too). If the tooling allows you to Go To Definition then a lot of the issues with knowing what they're for go away. But their conciseness for certain situations is massively valuable. It could open a door to a ton of useful operators for functional programming (for example). In my library I have an
But surely this is already an issue with overridden implementations of existing operators? If I change the |
Beta Was this translation helpful? Give feedback.
-
That's not quite it. What code users write depends a lot on what tools you give them and what tools you don't give them. For example, if you give them the option to define custom operators, but not to overload them, then you'll end up with That's what "pit of quality" means: when you give your users some tools, you need to make sure you're giving them the right ones and especially that you're not missing something.
It's not. On the syntactic level, the expression
It's more complicated, because the choice between an extension method, an instance method or even a delegate property invocation can happen at the semantic level. On the syntactic level, |
Beta Was this translation helpful? Give feedback.
-
That's a massive dose of hyperbole right there. You're essentially saying users can't be trusted, as I stated. And you're also implying that all other languages that allow you to define custom operators have fucked up. It's a ridiculous position to take.
Another massive dose of hyperbole. The expression doesn't have to be: BinaryExpression(SyntaxKind.AddExpression, IdentifierName("a"), IdentifierName("b")) It could be: BinarySpecialOperatorExpression(OperatorName("==>"), IdentifierName("a"), IdentifierName("b")) Anything can be deferred until the semantic analysis. The only issue I can see is what was raised by @CyrusNajmabadi:
Because of it being context free it makes it impossible to ascertain when building the
That's not an ideal solution, but it would give an implementation path that seems achievable. |
Beta Was this translation helpful? Give feedback.
-
In short, yes. That's precisely why the C# team carefully chose a very limited list of overloadable operators, they watched users of other languages screw it up, whether out of inexperience or out of being "clever". I, for one, don't want C# to turn into something that looks like my cat walked on the keyboard. Nor do I want to deal with any languages trying to hack in all of the lunacy and subtlies if the English language into some kind of DSL that is incomprehensible in all but the simplest cases. If I wanted either of those I have any number of other languages I could choose from. |
Beta Was this translation helpful? Give feedback.
-
I wouldn't phrase it that way, but okay, to some degree, yes.
I have never said anything like that. I'm saying that when adding new feature, you have to carefully evaluate it, consider how likely it is that it's going to be misused and think if it aligns well with the goals of your language. Maybe other languages with custom operators have different goals. Maybe their custom operators are not misused because of some other feature or because of their target audience. Maybe custom operators are not likely to be misused, period. I don't know. What I do know is that concerns about misuse should not be dismissed.
It's not hyperbole. It's an issue some implementations of custom operators have, including those that let you specify how an operator syntactically behaves (mostly associativity and precedence, as you noted), or those where any identifier can be operator. And the quote by @CyrusNajmabadi you originally reacted to was talking about the latter, where |
Beta Was this translation helpful? Give feedback.
-
Can you link me to the discussion on this? I am interested what rationale the C# team used when deciding that other languages had screwed up.
I agree. The solution is to not use the libraries that abuse the power. Just like I wouldn't use a library with a type like this in it: public interface _
{
_ _(_ _);
_ __(_ _);
_ ___(_ _);
_ ____(_ _);
_ _____(_ _);
} (Yes, that's valid C#)
Most language lexers have a set of valid operator characters [1]. Doesn't seem to be too difficult to decide if something is an operator or not. That may be the case when using infix identifiers, but I struggle to see how that's the case with punctuation characters. For identifier based operators then following a similar approach to Haskell and surrounding the identifier with backticks would work. [1] Example |
Beta Was this translation helpful? Give feedback.
-
@louthy when you bake fixness, associativity and precedence into the operator token, this system starts to restrict your operator choice almost just as much as having a handful overridable operators does. Let's say I want to implement a PEG DSL. What I wouldn't mind at all is getting a few more pre-baked operators for the functional stuff, like 'pipe', (which I'm prototyping for my own amusement), 'compose' and 'bind'. |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox Yep, it's certainly not ideal. I was wondering if there could be an alternative approach (this isn't fully thought through so be nice!). Some of the operators I'd like to see should be pre-baked as you say (forward and backward composition, bind, material implication ( For example, if I have an var ma = Some("abc");
var mb = Some("def");
var mc = ma + mb; // Would you expect Some("abcdef") ? What about: var ma = Some(Guid.NewGuid());
var mb = Some(Guid.NewGuid());
var mc = ma + mb; What I want to be able to do is define the monadic plus operator Something like: var ma = Some("abc");
var mb = Some("def");
var mc = ma <+> mb; Would be great. Because it looks like a var ma = Some("abc");
var mb = Some("def");
var mc = ma <+> mb; // Some("abc")
var ma = None;
var mb = Some("def");
var mc = ma <+> mb; // Some("def")
var ma = Some("abc");
var mb = None;
var mc = ma <+> mb; // Some("abc") So I wonder if we could have a set of sanctioned operators that aren't used in the BCL, but can be overridden in the usual way that represent 'boxed' versions of current operators:
They would then have a contextual meaning: operators that work on the container objects, and could even share the same precedence and associativity as their non-boxed counterparts. That obviously doesn't solve all of the problems you mentioned, but it may lead to a more controlled operator environment compared to the custom-operator free-for-all. I'd really love to see generic operators too. That combined with the suggestions above would solve most of the problems I run into. |
Beta Was this translation helpful? Give feedback.
-
@louthy Specifically for The |
Beta Was this translation helpful? Give feedback.
-
@svick Why would
I don't assume anything about anybody. I am looking for solutions to real world problems that I face.
But that's the point. I have already generalised monads using the 'concepts' approach. I'm hoping that if in the future we get the shapes feature that we'll also get shape operators, which means with this approach I would be able to simply define I realise I'm fighting at the edges of C# here, I get it, but a more powerful operators story would really facilitate expression oriented dev greatly (imho). Which is my main driver when contributing to these discussions, I want to be able to make everything I do into an expression, it's so close and improving all the time, but stuff like |
Beta Was this translation helpful? Give feedback.
-
I did not realize that your |
Beta Was this translation helpful? Give feedback.
-
Sorry for a bit of a necropost, but this is still a useful discussion. I'm going to suggest a possibly controversial idea for this: Only allow operators to be given currently valid names. The suggested |
Beta Was this translation helpful? Give feedback.
-
This is something that I've always wanted to do, and loved working in Unreal Engine's visual scripting because you could make the functions look like the default operators. Power operators and percent ones were really useful, and then I made a lot of others. The trouble is when we have stuff like this: int x = y <^v> z // what's going on? But with named operators we can much better tell what they are going to do in a less subjective way. However, I find it's really more just of a method at that point. double a = y%;
double b = y percent;
double c = y.percent(); So I would prefer having them be symbols and get the benefit of typing a one-to-two letter thing and it working. Besides, if you don't understand what it is, you can just open up the method with F12 and view it. |
Beta Was this translation helpful? Give feedback.
-
This is like saying that method names can be cryptic because anyone who doesn't understand what it means can just open up the method with F12 and view it. I disagree. Readability of code is critical - it's read many many more times than it is written. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
In Swift, there can be operators that operate on 1, 2, or 3 values. We call those operators unary, binary, or ternary (respectively).
Swift has 4 types of operators, 3 of which are available for us to use with custom operators they are:
prefix A unary (operating on one value) operator that is just before the value it operates on.
postfix A unary operator that is just after the value it operates on.
infix A binary (operating on two values) operator placed between the two values it operates on.
Reference : http://www.codingexplorer.com/custom-operators-swift/
It would be great if C# Next support for writing custom operators, can be a way to access DSLs in .NET.
The syntax can be like
Beta Was this translation helpful? Give feedback.
All reactions