I'm curious, could you elaborate?
I'm curious, could you elaborate?
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.
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).
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.
Can you say so decisively for a language with first-class functions?
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.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...
foo :: (Int, Int) -> Int
foo (x, y) = ...
> 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.
On the other hand the enum thing is certainly surprising.
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 )
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.