Add Automatic Generalization like F#?

In F#, we can bind a function without type definition like this:

let max a b = if a > b then a else b

And use like this:

let biggestFloat = max 2.0 3.0   
let biggestInt = max 2 3  

Because max is generic, it can be used with types such as int , float , and so on.
Is it possible to support in rust ?

Automatic Generalization msdn doc:

Standalone functions in Rust are required to have type annotations since this makes code self-documenting and and speeds up type-checking. I doubt that'll ever change and I know a lot of us wouldn't want it to change.

Closures can omit type annotations though. Currently closures can't be generic but that will probably change at some point. Once it does it should be possible to support writing generic closures without type annotations. eg. In your example the closure would be inferred to have type for<T: PartialOrd<T>> Fn(T, T) -> T.

1 Like

Even if it doesn’t change I’d be delighted if I could write something like

fn max(a: _, b: _) -> _ {
    if a > b { a } else { b }
}

and get a compiler error that tells me exactly what the most general type signature is to support this, i.e.

fn max<T: PartialOrd>(a: T, b: T) -> T

and I could automatically have my IDE insert that for me.

For this example, one would probably actually want to use Ord, but that’s an easy refactor after the fact.

13 Likes

Automatic Generalization in F# is optional, we can also write like this.

let foo: int = 1;

Having coded a lot in OCaml, I have come to prefer the more verbose Rust version. In my experience, it removes ambiguity, avoids a number of hard-to-explain corner cases (the dreaded '_a) and serves as documentation.

That being said, I like @steffahn 's suggestion.

2 Likes

Note that it's an intentional design choice to require types on functions, so it's unlike that that would change.

So generally right now the way to do that in Rust is via a macro. And if https://github.com/rust-lang/rfcs/pull/3106 were accepted, that would be even simpler to do.

1 Like

This is probably an artificially simple example. After all, traits are based on Haskell’s type classes, and type classes were designed specifically for operator overloading. Furthermore Haskell does support type inference of generic polymorphic functions, so those can be defined (syntactically) very similar to the F# example OP gave above. In Haskell type signatures are optional in general, and almost all type signatures are redundant since they can/could be inferred. So no wonder everything seems rather straightforward in a setting that corresponds 100% to something that Haskell can do.

Some significant problems only appear once you start looking at the extensions that Rust has over Haskell. In particular methods are problematic. Unlike the setting with operators, method name resolution is type-based: you can’t always tell which type class trait is involved when you have an expression x.foo(). The method foo can be present in multiple traits and even be an associated method of multiple specific types. For the latter case, the type doesn’t even need to be “imported” in any sense.

In contrast, Haskell doesn’t support methods at all, only normal “freestanding” functions, where you’ll always know from the name alone which function with what input type or belonging to which type class you’re referring to, as functions needs to be imported, and unambiguous or qualified by their module name.

3 Likes

I think there's merit to be able to automatically deduct trait bound on generic parameters, as least for non public interfaces. It's a pain currently to write generic math code that involves a lot of looking up trait bounds in num-traits.

1 Like