Dhghomon / rust-fsharp

Rust - F# - Rust reference
MIT License
239 stars 14 forks source link

Add section on `inline` keyword? #1

Open isaacabraham opened 3 years ago

isaacabraham commented 3 years ago

The section that discusses the printByte function mentions (correctly) that F# will infer the input type based on usage, or will fall back to e.g. int.

This time it will refuse to compile, because it has determined that it needs an int and now we've told it to also take a byte type as well. The former input has set the variable input type in stone and it duly refuses

You can actually make the function generic for any type that supports %i by using the inline keyword:

let inline printByte number =
    printfn "%i" number

which creates a function as follows:

val inline printByte :
  number: ^a -> unit
    when  ^a : (byte|int16|int32|int64|sbyte|uint16|uint32|uint64|nativeint|
                unativeint)

Not sure if you want to add this in - but may be useful to know that F# can do this.

Dhghomon commented 3 years ago

Thanks, I actually saw this in a video yesterday for the first time and was curious about how it worked - I'll put this in the document too.

One other interesting thing I saw was a static keyword I think that was used to give record types things like the + operator (written with (+) I think) which looks a bit similar to Rust traits (basically you implement a trait for your type and you get all its functions, and then generic functions can take it too because the behaviour is now predictable). Ben Gobeil was showing that in a video.

Dhghomon commented 3 years ago

Okay, here's the addition:

https://github.com/Dhghomon/rust-fsharp/commit/900367875f64748f41ff216e7114f03d08ccb195

I am guessing a bit here but it looks like this is the F# version of what we call static dispatch in Rust (which is the more common way to use generics). The compiler does a bit more work up front but then you end up with one concrete function for each type we tell it to call, and that makes it generic from our point of view while also making the runtime fast. (Increases the binary size a tad too)

isaacabraham commented 3 years ago

One other interesting thing I saw was a static keyword I think that was used to give record types things like the + operator (written with (+) I think)

That's right. F# has some features that use structural typing, rather than nominal, including the (+) operator. So if you have a record or some DU with the static (+) function implemented, you can then do things like Seq.sum on them and it'll call that function.

But this is somewhat unusual in F#, most of .NET is nominal - if you want to do this kind of structural typing stuff in F# you need to start going outside the box using somewhat obscure features in the language like SRTPs - don't go there :-)

grenaad commented 2 years ago

I like to think that traits are comparable to a combination of Java/C#'s interfaces and extensions methods. That been said, the static dispatch is basically a constrained on the generic type. While the F# inline does not require to explicitly add this constraint, it is inferred. e.g

let inline add x y = x + y
add 1 2
add "a" "b"
add false "a"  // error: the type string does not match the type bool

The rust equivalent would be to explicitly add the Trait std::ops::Add constrained on the generic types x and y. It is a small difference, but I think noteworthy.