Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upInfix notation for function call / invocation. #1579
Comments
This comment has been minimized.
This comment has been minimized.
|
This is a TIMTOWTDI feature with very little (any?) gain. It doesn't read terribly well either. |
This comment has been minimized.
This comment has been minimized.
|
Also discussed in: rust-lang/rust#8824 |
This comment has been minimized.
This comment has been minimized.
|
TIMTOWTDI = There's more than one way to do it There's nothing inherently wrong with having more ways to do one thing. |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Apr 13, 2016
|
The idea is to introduce one more operator to end all custom operators. Instead of defining custom operators, we can just use functions for this purpose. I know the argument that methods are infix,
|
This comment has been minimized.
This comment has been minimized.
ketsuban
commented
Apr 13, 2016
|
It doesn't seem beyond the wit of man that someone might reasonably expect a library-defined operator whose return type is the same as that of the first operand to be usable as an augmented assignment. How easy is that to do? |
This comment has been minimized.
This comment has been minimized.
|
Discussion on fixity from IRC, for future reference...:
|
This comment has been minimized.
This comment has been minimized.
bungcip
commented
Apr 13, 2016
|
I like custom infix operator as long as limited in ascii letter. But I don't like if you can turn any arbitrary function to infix call. When I create function see previous discussion in https://users.rust-lang.org/t/infix-functions/4724 |
This comment has been minimized.
This comment has been minimized.
|
Does it interact with generics? Would we end up with |
This comment has been minimized.
This comment has been minimized.
|
@durka Probably, you'd have to specify it somehow with generics, and what you've come up with seems best. But I guess you'd save |
This comment has been minimized.
This comment has been minimized.
|
I don't want this in the language. I think it adds a lot of complication, and it's unimportant to me personally. However, I wouldn't be too opposed to this. It seems nice, especially, for mathematical code. |
This comment has been minimized.
This comment has been minimized.
|
Infix functions are great in languages like Haskell where operators are more common, but it doesn't seem to give Rust much. |
This comment has been minimized.
This comment has been minimized.
alilleybrinker
commented
May 27, 2016
|
Are there examples of projects where this would result in a decent ergonomic improvement? |
This comment has been minimized.
This comment has been minimized.
|
@AndrewBrinker I venture a guess that writing mathematics libraries, e.g: linear algebra, and using them, as well as the low level parts of physics engines might benefit from the increased ergonomics. There might be other projects of course, but off of my head, that's the typical use case. |
This comment has been minimized.
This comment has been minimized.
|
@Centril The existing overloadable operators covers 95% of those use cases. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
May 27, 2016
|
Would this mean we could write obfuscated code that compiled both in Rust and TeX? ;) I love the infix operator notation in Haskell, partially because not all mathematical operators to *, +, etc. even when mathematicians write them that way. I love Haskell's lack of parentheses on function calls too though. Yet, Rust is a rather different language, so tools like currying do not always fit. At first blush, I'm thinking one should start with just knowing when to avoid, or argue against, using object I fear this proposal makes the left vs right action issue worse. In other words, In the long run, one could probably build a strict functional language with Haskell-like syntax, maybe an Idris fork, that compiles to Rust code, but does not introduce a garbage collector. I'd suspect that's a better way to build DSLs that compile to Rust. |
This comment has been minimized.
This comment has been minimized.
alilleybrinker
commented
Aug 12, 2016
|
It strikes me that, although it wouldn't be super pretty, procedural macros could be used to provide as flexible a mathematical notation as is desired, without the introduction of infix function application. Actually, looking at the IRC log @Centril includes above, it seems this idea has been mentioned previously. It would be some extra work, and would be strictly-speaking less flexible than infix function application, but it is something that would work today without changes to the language. |
This comment has been minimized.
This comment has been minimized.
Shou
commented
Aug 17, 2016
|
I was going to post this in #818 but it's more appropriate here. I'm in favour of infix functions. User definable operators can be a bit tough to wrap your head around depending on how they're defined, but simpler, more rigorous operators like -- Operators
f $ g $ h $ 10
=
-- Backtick infix functions
f `apply` g `apply` h `apply` 10
=
-- Math-y infix functions
f apply g apply h apply 10
=
-- Dot functions; not very applicable here
f.apply(g.apply(h(10))
=
-- Normal functions
f(g(h(10)))
let f = g . h
=
let f = g `compose` h
=
let f = g compose h
=
let f = g.compose(h)
=
let f a = g(h(a))
let newList = n `insert` oldList
=
let newList = n insert oldList
=
let newList = oldList.insert(n)
=
let newList = insert(n, oldList)What about functions of >2 arguments, though? It's actually doable with auto-currying which is a topic that has been discussed quite a bit for Rust. This is specifically useful when you're doing higher-order programming, and sending functions around to other functions, or returning functions; like let insertValue = (`mapInsert` value)
let addPair = insertValue(key)
addPair(hashMap)
=
(key `mapInsert` value)(hashMap)
=
mapInsert(key, value, hashMap)We also have to think about the default associativity and precedence of infix functions. In Haskell they are left associative and have the strongest precedence so they bind tightly. Maybe it would be nicer if they bind weakly instead? And should it be user definable? Assuming it is, how would that be made verbose to the user? It wouldn't be easy to figure out without attaching it to the name of the function itself as far as I can think, so infix functions with different associativity and precedence can be a bit opaque to users, which is alike the argument against user-definable operators. -- Tight precedence
(10*n+1) `insert` myList
-- Weak precedence
10*n+1 `insert` myListThese are tradeoffs in tightly binding vs weakly binding by default. |
nrc
added
the
T-lang
label
Aug 17, 2016
This comment has been minimized.
This comment has been minimized.
Yes, this seems to be the central problem... It is solvable, but one has to decide which way to go...
This sounds like an awesome idea! It could just compile to
The problem with procedural macros here is that you have to use the procedural macro, inducing the visual overhead of the macro itself. Having to do:
One has to remember that unlike rust, haskell is a lazy garbage collected language. Rust is both eager and has manual memory management. Thus, every time you partially apply a function, its arguments must be moved to the heap, and then you have to deal with |
This comment has been minimized.
This comment has been minimized.
|
I think by now, enough has been fleshed out to create an RFC out of this issue... what does @bluss think? There is one area in particular where some help is needed...
|
This comment has been minimized.
This comment has been minimized.
comex
commented
Aug 18, 2016
•
Lambdas are not automatically/implicitly moved to the heap, only if you explicitly box them (and with |
This comment has been minimized.
This comment has been minimized.
|
(Maybe this discussion is getting off-topic?)
True, my bad. However, if you return the function itself, then that function's arguments must be boxed & It follows that if you want to pass the lambda to another function as an argument that the same logic applies. In most cases (this is just a hypothesis) where you'd want to partially apply a function, you either want to return the function or pass it to something else. Purely partially applying a function and using it within the same stack frame doesn't seem like a big use case to me. |
This comment has been minimized.
This comment has been minimized.
tobia
commented
Jan 15, 2017
•
|
I like the IMHO this syntax should only support binary functions. I would also recommend against any precedence or associativity declarations, like
That would be a nice touch. |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Jan 15, 2017
•
|
Methods are functions, so |
This comment has been minimized.
This comment has been minimized.
|
I want to argue against this or anything even vaguely like this, on legibility grounds. People are going to write
and nobody, including the original author six months later, is going to have any idea what it means. As a rule of thumb, a mixed chain of infix operations is illegible unless there is an operator precedence rule that everyone not only agrees on, but has had drummed into their head since elementary school (i.e. Declaring that "all such operators have the same precedence and are universally (right|left) associative" does not help, because that rule hasn't been drummed into everyone's head since elementary school, so reading it that way is not automatic. |
This comment has been minimized.
This comment has been minimized.
I thought about this some more, and my concerns can be addressed with a little syntactic salt and a function annotation. There are two legibility problems with user-defined operators. One is that the precedence of user-defined infix operators, relative to any other operators (user-defined or not), is unclear: what does Now, mathematicians make up operators all the time — how is this not a problem for them? They always parenthesize, unless both precedence and associativity are unambiguous. They have a fairly broad idea of "unambiguous precedence"; I suspect a mathematician would say that of course dot product has higher precedence than scalar addition. However, their idea of "unambiguous associativity" is quite narrow: if a mathematician writes So I propose the following rules:
|
This comment has been minimized.
This comment has been minimized.
|
@zackw Thank you for a very well thought-out and reasoned reply! |
This comment has been minimized.
This comment has been minimized.
|
Bikeshedding: a \dot bvs. a `dot` bvs. other syntax ? |
This comment has been minimized.
This comment has been minimized.
|
Just my opinion: backticks should be reserved for some future hypothetical kind of string literal. |
This comment has been minimized.
This comment has been minimized.
|
@zackw seams like a reasonable objective reason to prefer:
I like that your reasoning is not just based on subjective taste =) |
This comment has been minimized.
This comment has been minimized.
|
@Lokathor I've now checked on how it is on a French° keyboard and on an US keyboard. And they all have different ways for writing accented letters! Interesting! xD ° The German keyboard has those accents only for foreign languages like French |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
May 2, 2018
|
On Nordic/Scandinavian keyboards the ` symbol is really hard to come by: shift + acute accent button and after that, a space bar (because it's a dead key). |
This comment has been minimized.
This comment has been minimized.
|
@golddranks The position of the key (to the left of backspace) and how it operates seems to be exactly the same as on the German keyboard. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
May 2, 2018
|
Apologies for the derail, but.. How would you do infix operators in a curried ML-style language, without using
or
with the second form being an outer match if multiple forms were provided. You could still invoke I think this bare word makes syntax highlighting not context free, and not necessarily even determined by the file, which complicates editor tooling. Worse, it creates the same complexity for humans. Worse still, these languages represent type parameters like value parameters, so more realistic syntax becomes:
with some early parameters being implicit or inferred when used, which matters when you represent traits with modules. I'm not even sure if Haskell permits expressions as operators, ala |
This comment has been minimized.
This comment has been minimized.
|
I can't follow your code examples. Is this Haskell? Quick recap: The syntax proposed here is intended for method calls. See
I think syntax highlighting can still be done without type information even if there is no marking like |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
May 2, 2018
|
@burdges but... Rust isn't a curried ML language? I'm not sure how any of your statements would apply to Rust. I'm not even sure how most of them would apply to Haskell. In Haskell, you can only make a prefix function into an infix operator if it's a single binding anyway:
|
This comment has been minimized.
This comment has been minimized.
|
@Lokathor fun thing - because haskell is a curried language, you could do
also, I'd love to have this feature no matter what it looks like. |
This comment has been minimized.
This comment has been minimized.
|
@ubsan You could |
This comment has been minimized.
This comment has been minimized.
|
@Centril tell that to the wonderful people behind lenses :3 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ekmett
commented
May 5, 2018
|
@Centril: In (.~) :: Lens s t a b -> b -> s -> t
(%~) :: Lens s t a b -> (a -> b) -> s -> t
(+~) :: Num a => Lens s t a a -> a -> s -> tThe common idiom is to use them like
The operators are binary operators that return functions, and You could also use them in a fashion like
to build a pipeline of mutations, but this is a less common form. As far as Rust is concerned, I have no dog in this fight, but in Haskell it works quite well and I wouldn't consider it unidiomatic to pipeline functions together in a functional language. ;) |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
May 6, 2018
•
Nor do I, it's not about the ability to build function pipelines. My arguments were about the unnecessary nature of infix notation, since one can already achieve piping with existing language features, namely method call syntax. Also, Rust is still a different language, and it's not an automatic positive or an obvious gain to make it more like Haskell. Haskell's got many things right, but its syntax is certainly not a part I would praise. Most of the good stuff lies in the semantics of the language, and Rust has borrowed many parts of it already, notably type classes. For which I'm glad — but I'm also glad Rust's designers didn't copy over the weird syntax along with it. |
This comment has been minimized.
This comment has been minimized.
Pipe-lining with method call syntax fails in the face of free functions and borrowing.
But understand that this is purely subjective. I happen to love Haskell's syntax. There are no objective reasons for why Rust's syntax is better or why Haskell's is better. It is all about what you are used to. Rust's syntax is as it is and not like OCaml's since it was purposefully designed in such a way to not waste the complexity budget on unfamiliar (to C++ programmers) lexical syntax. |
This comment has been minimized.
This comment has been minimized.
tobia
commented
May 6, 2018
|
One feature I love from OCaml (and Haskell too, if I'm not mistaken?) is that you have a set of characters to build names with, including function names, which is the usual letters-numbers-underscore plus the single quote, and another set of characters to build operator symbols with, which is a subset of the ASCII symbol characters. So when you see Maybe we could solve the issue like this and make math library authors happy as well? I still think custom operators should have a fixed precedence level and no associativity (= forced parentheses.) Incidentally, I happen to love OCaml syntax, except for a few weird points. To me it's a good middle ground between Haskell and C-like languages. (Yes, I know OCaml came before Haskell, what I mean is that to me the latter is weirder, not easier.) |
This comment has been minimized.
This comment has been minimized.
Haskell came before OCaml :)
|
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
May 6, 2018
This seems to be a common misconception among programmers; unfortunately, it's false. There are objective reasons, especially when it comes to robustness and fault tolerance, for why one kind of syntax is superior to another. For example, whitespace sensitivity, and in particular, indentation-based blocks are dangerous since transmission through a channel that doesn't respect whitespace can significantly change the meaning of a program. So, no, syntactic choices are not "purely" subjective. And even if they were: why do you feel your "I like Haskell's syntax" argument is stronger than my "I don't like Haskell's syntax" viewpoint? |
This comment has been minimized.
This comment has been minimized.
Wyverald
commented
May 6, 2018
•
|
Before things get more fiery, I think y'all are arguing the same point -- to quote H2CO3:
I don't think anyone is saying their preference matters more, just that there are views on both sides (and both views are partially, if not purely, subjective). |
This comment has been minimized.
This comment has been minimized.
Are there any scientific studies to this effect?
Personally, I think it is dangerous to rely on lexical syntax for fault tolerance and robustness; I would much rather as much as possible rely on a type system that makes fragile things not well typed. From my time as a teaching assistant for a beginners course in OOP (Java), I think it is equally easy to misplace braces and parenthesis. My view is that we really use layout syntax even with braces (based on how we format with rustfmt), and that they mostly are redundant noise in the way of reading, but I understand that this is my subjective preference and not a universal constant.
I did not make this claim :) |
This comment has been minimized.
This comment has been minimized.
|
Please keep off-topic comments about Haskell's syntax out of this Rust thread :) |
This comment has been minimized.
This comment has been minimized.
gbutler69
commented
May 6, 2018
•
I'm not a Haskeller, but, it is difficult for me to understand why you would consider the comments about another language's syntax, that clearly falls into the category of "Prior Art", to be off-topic on the discussion thread for a proposed feature/RFC for Rust. Sounds more like trying to silence someone who you disagree with rather than arguing the merits one way or another. Odd thing indeed. I'm highly offended by the smugness of telling someone they should shut-up because you disagree with what they have to say. |
This comment has been minimized.
This comment has been minimized.
|
@gbutler69 Nobody is telling anyone to be quiet, because this is an RFC issue and the "C" stands for "comment". Also, please read @ubsan comment again and you'll see that there is no malice intended by the words she uses. Objectively there's also nothing wrong with wanting to stay on-topic. I gave @ubsan comment a thumbs up because the debate about Haskell syntax is really hard to follow. It'd be helpful if the relevant parts were explained, so that a Haskell noob like me could follow. I'm just seeing a bunch of squiggly infix operators xD |
This comment has been minimized.
This comment has been minimized.
gbutler69
commented
May 6, 2018
That's the problem though. As I've said, I just can't understand how they could be considered off-topic. If all you need to do is call something "Off-Topic" without justification to say that it shouldn't be included in the discussion, that is tantamount to just saying, "I disagree, so, shut-up". I don't particularly care for that. If that wasn't the intent, then, my apologies for misreading, but, understand, that is how it comes off to myself and probably many others as well. I'm sure others will perceive it differently though. |
This comment has been minimized.
This comment has been minimized.
gbutler69
commented
May 6, 2018
In that case, asking the commenter to clarify is highly desirable and appropriate. Calling it off-topic and saying that the comments don't belong because they are off-topic is not. Again, "Prior-Art" is 100% on-topic in any proposal. I just can't see how it couldn't be. |
This comment has been minimized.
This comment has been minimized.
|
I'm suspecting @ubsan was half-joking?; I don't mind their comment at all
Sure thing! (and some bonus parts) EDIT: I hope this was helpful / understandable; if it wasn't, let me know =) Let's start simple. -- Equivalent (up to memory representation) to `enum List<a> { Nil, Cons(Box<List<a>>), }`.
--
-- List is a type constructor from Type -> Type;
-- List Int is applying 'Int' to 'List' giving you back a type.
-- 'a' is a type variable (generic type parameter)
-- 'Nil :: List a' is a data constructor
-- 'Cons :: a -> List a -> List a' as well.
data List a = Nil | Cons a (List a)
length :: List a -> Int -- A function from List of 'a's to 'Int'
length Nil = 0 -- pattern matching on Nil
length (Cons _ xs) = 1 + length xs -- Pattern matching on Cons.
-- explicit quantification of the type variable 'a'
length :: forall a. List a -> Int
length xs = case xs of -- similar to 'match'
Nil -> 0
Cons _ xs -> 1 + length xs
-- A binary function from 'a' to 'a' to 'a'
-- proviso that 'a' satisfies the 'Num' typeclass (trait);
plusMul2 :: Num a => a -> a -> a
-- This is the same; Haskell functions are curried!
plusMul2 :: Num a => a -> (a -> a)
plusMul2 x y = (x + y) * 2Rust equivalent of the last one (up to currying, laziness, memory model..): fn plusMul2<A: Add + Mul>(x: A, y: A) -> A {
(x + y) * 2
}With respect to the example lens operators: (+~) :: Num a => Lens s t a a -> a -> s -> t
-- explicitly quantified type variables and explicitly showing the currying:
(+~) :: forall a s t. Num a => Lens s t a a -> (a -> (s -> t))This declares (the type of) the infix ternary custom operator whatever = (field3 %~ reverse) . (field2 .~ baz) . (field1 .~ bar)This defines a top level function More details: https://hackage.haskell.org/package/lens |
This comment has been minimized.
This comment has been minimized.
|
In Kotlin, functions marked with the |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
May 8, 2018
•
|
Here are a few arguments for placing this feature in a library rather than in the core language The syntax looks slightly alien compared to the rest of rust and it is not very googleable, but if we force the programmer to decorate each function with I will probably only be using the feature in a few math-heavy functions, so at least for me it doesn't require a lot of work to decorate each function separately. If we end up with several competing macro libraries with varying syntax, then we have a change to pick the best one. |
This comment has been minimized.
This comment has been minimized.
Certainly no more alien than the operators the language already has?
Haskell uses custom search engines (hoogle) to provide you with much better results than google could ever provide. I believe we could do the same. Decorating functions with a proc macro such as this has the problem that proc macros are a very heavy weight DSL authoring mechanism; it costs a lot to make such a DSL. Meanwhile, custom operators, or functions with infix function notation such as in Haskell are extremely easy to make new ones of. |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
May 8, 2018
•
|
Would it be possible to define a macro for defining infix dsls? Something like this could be useful. // In library
pub fn dot( .., ..) -> .. { ..}
pub fn cross( .., ..) -> .. { ..}
define_infix_dsl!(my_linalg, [dot, cross])
// In code
#[dsl(my_linalg)]
fn foo() {..} |
This comment has been minimized.
This comment has been minimized.
Let's experiment ;) |
This comment has been minimized.
This comment has been minimized.
|
@nielsle In a DSL, the infix call style could just be enabled for all methods (with a |
Centril commentedApr 13, 2016
There doesn't seem to be much support for supporting custom operators (see #818) in Rust. But what about calling normal binary functions as if they were operators, like haskell allows you to?
The syntax would be:
a `dot` bto convey scalar product of two vectors a and b.
Another option suggested by @bluss:
Or if it possible without parsing conflicts (@thepowersgang: "but probably not dersirable"):
Which is converted to
a.dot( b )or, depending on the function:
dot( a, b )