3 ms·
In Rust, a function definition left-hand-side looks like an annotated pattern, e.g. foo(x : int) Therefore, one would expect to annotate the return type as,
by willtim 5y ago
In Rust, a function definition left-hand-side looks like an annotated pattern, e.g.
foo(x : int)
Therefore, one would expect to annotate the return type as,
foo(x : int) : string
Since the pattern is showing foo applied to x. The Rust syntax is actually confusing for both Haskell/ML programmers (where the arrow comes from) and mainstream programmers. It's too small an issue to change now though.
Rust's support for proper "algebraic data types" is very good and gives it an advantage over languages like C++. However there are some small surprises, such as forcing all enum constructors/fields to be public (one must therefore wrap it to make an abstract data type).
Every language has its warts and these are particularly minor ones.
- devit 5y agoThat would imply that "foo(x: int)" is a string rather than a function. Haskell doesn't use that notation either, it uses -> both for the parameter list and for the return type, and separates argument names (arguably, these are poor choices, since currying is not an efficient CPU-native operation and not intuitive so distinguishing between multiple arguments and returning closures is useful, and argument names are useful for documentation).
- valenterry 5y agoI don't think that would be the implication, because we are in the context of a function. Otherwise, we should also not write foo(x: int) but foo: int -> ?
- willtim 5y ago> That would imply that "foo(x: int)" is a string rather than a function But foo(x : int) is a string! It literally reads "foo applied to x". In the function definition, it appears to be used as a left-hand-side pattern which is "matched". The definition is written as if to say, whenever the term foo(x) is encountered, use this definition here. At least, that was my expectation. > Haskell doesn't use that notation either OCaml does and Haskell once had a proposal to add it. Haskell type signatures are normally written separately, but it does support annotating patterns with the right extensions.
- ZoomZoomZoom 5y ago> But foo(x : int) is a string! Can you say so decisively for a language with first-class functions?
- willtim 5y agoYou are quoting me out of context. In many languages, the term and/or pattern foo(x : int) is a string, if foo : int -> string.
- gpm 5y agoNo, foo(x: int) is not a string, it's not even an expression, it's not even an AST node. It's a fragment of the larger ast node fn foo(x: int) -> ReturnType { body } The ast here splits into Function { name: foo signature: (x: int) -> ReturnType body: body } I.e. the arrow binary op binds more tightly than the adjacency between foo and x: int. And the type of foo is a function, not a string. A "better" way to write this (in that it breaks down the syntax into the order it is best understood) might be static foo: (Int -> ReturnType) = { let x = arg0; body } Or to put it another way. Reading foo(x: int) as "foo applied to x" in this case is a mistake, because that's now how things bind. You should read that "foo is a (function that takes Int to String)". It's a syntactic coincidence that foo and x are beside eachother, nothing more.
- willtim 5y agoThat's a nice explanation of what's going on. My point remains that I found the syntax confusing though.
- gpm 5y agoYa, I'm not really going to defend the current syntax past "function syntax is hard". It's mixing up assigning a global variable, specifying that variables type, and destructuring an argument list into individual arguments, in one line. I've played at making my own language, and this is one part that I've never been satisfied with. Personally I'd probably at least go with a `foo = <anonymous function>` syntax to split out the assigning part. But that's spending "strangeness budget" because that's not how C/Python/Java do it, and I can understand the decision to not spend that budget here...
- kaba0 5y agoJust a nitpick, but currying is a language detail, one can potentially create an efficient implementation with or without them.
- willtim 5y agoYes and of course Haskell is quite capable of supporting uncurried functions too, e.g. foo :: (Int, Int) -> Int foo (x, y) = ...
- the__alchemist 5y agoFYSA, The Rust approach is the same way it's done in Python.
- willtim 5y agoWell the Python community is hardly an authoritative figure on static typing :)
- acdha 5y agoThis is true but Python has a user community which is several orders of magnitude broader, which confuses the issue a bit. These days if I was designing a language I'd probably ask “How would I explain this to someone who learned JavaScript/Python?” since even if you have great reasons for doing things differently it's a pretty reasonable way to predict sources of confusion for newcomers.
- ModernMech 5y agoI wouldn't say this is a wart or confusing (to me at least). As someone who uses Haskell, C++, and Rust regularly, I just accept that each language has its own syntax. It's true that Rust borrows ideas from many languages, but I view Rust's syntax as its own thing, and the meaning of the symbols are what they are. It doesn't have to do things the C++ way or the Haskell way. It does things the Rust way, and that's not a wart.
- willtim 5y agoIt did confuse me when I first saw it, but yes, it is not really a significant issue.
- uryga 5y ago> one would expect to annotate the return type as > foo(x : int) : string > since the pattern is showing foo applied to x. kind of like the C/C++ "declaration mirrors use" thing, i.e. int *foo; which means "the result of dereferencing `foo` is of type `int`" which is not the same as saying foo : Ptr<int>; because in the former, you're kind of describing what `foo` is a without actually saying it, if that makes sense. i find that way of specifying types counterintuitive in both C/C++ and MLs.
- ape4 5y agoI typed `foo(x : int) : string` when I was starting and there was an error message telling me to use `-> string` so many people expect this syntax.
- jeltz 5y agoHaving used both Haskell and main stream programming languages I did not at all think that was confusing. The type of "fn foo(x: int) -> string" is quite obviously "fn(x: int) -> string" for people coming from languages like C. I do not see how a colon would make anything more clear. Imagine the function "fn bar(x: fn(x: int) -> string)", would that be more clear with a colon? On the other hand the enum thing is certainly surprising.
- willtim 5y ago> Imagine the function "fn bar(x: fn(x: int) -> string)", would that be more clear with a colon? In your example, why bother naming the inner "x" variable for the function param? It cannot be used on the right-hand-side (definition of "bar"). For that reason, the notation is not exactly "clear". In OCaml the annotation would be: bar( x : int -> string )
- east2west 5y agoIn Rust you can write ```f: fn(i32) -> i32```. Ocaml's syntax is more consistent, I agree, but its colon operator has different precedence than in Rust, so I am not sure its rational applies to Rust.