F# generics / function overloading syntax

2019-01-07 21:25发布

问题:

I'm confused on how to label a function as generic without an explicit type declaration like ('a -> 'a)

let add a b = a + b

This gives us

val add : a:int -> b:int -> int

However we can then immediately call

add "Hello " "World!"

and now the value of add is

val add : a:string -> b:string -> string
val it : string = "Hello World!"

If we then call

add 2 3 // then we get
error: This expression was expected to have type string but here has type int

How do I ensure that a function works on all types that say have the function (+) defined

回答1:

This is F#'s embarrassing skeleton in the closet.

Try this:

> let mapPair f (x,y) = (f x, f y)
val mapPair : f:('a -> 'b) -> x:'a * y:'a -> 'b * 'b

Fully generic! Clearly, function application and tuples work.

Now try this:

> let makeList a b = [a;b]
val makeList : a:'a -> b:'a -> 'a list

Hmmm, also generic. How about this:

> let makeList a b = [a + b]
val makeList : a:int -> b:int -> int list

Aha, as soon as I have a (+) in there, it becomes int for some reason.
Let's keep playing:

> let inline makeList a b = [a + b]
val inline makeList :
  a: ^a -> b: ^b ->  ^c list
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)

Hmmm, interesting. Turns out, if I make the function inline, then F# does consider it generic, but it also gives it this weird when clause, and my generic parameters have this strange ^ symbol instead of the usual tick.
This strange syntax is called "statically resolved type parameters" (see here for a somewhat coherent explanation), and the basic idea is that the function (+) requires its arguments to have a static member (+) defined. Let's verify:

> let x = 0 :> obj
  let y = 0 :> obj
  let z = x + y
Script1.fsx(14,13): error FS0001: The type 'obj' does not support the operator '+'

> type My() =
     static member (+)( a:My, b:My ) = My()
  let x = My()
  let y = My()
  let z = x + y
val x : My
val y : My
val z : My

Now, the problem with this is that CLR does not support this kind of generic parameters (i.e. "any type, as long as it has such and such members"), so F# has to fake it and resolve these calls at compile time. But because of this, any methods that use this feature cannot be compiled to true generic IL methods, and thus have to be monomorphised (which is enabled by inline).

But then, it would be very inconvenient to require that every function that uses arithmetic operators be declared inline, wouldn't it? So F# goes yet another extra step and tries to fix these statically resolved generic parameters based on how they are instantiated later in the code. That's why your function turns into string->string->string as soon as you use it with a string once.

But if you mark your function inline, F# wouldn't have to fix parameters, because it wouldn't have to compile the function down to IL, and so your parameters remain intact:

> let inline add a b = a + b
val inline add :
   a: ^a -> b: ^b ->  ^c
      when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)


回答2:

If I understand you correctly, use inline:

let inline add a b = a + b

add 2 3 |> printfn "%A"
add "Hello " "World!" |> printfn "%A" 

Print:

5
"Hello World!"

Link: http://ideone.com/awsYNI



回答3:

Make it inline

let inline add a b = a + b
(*
val inline add :
  a: ^a -> b: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^c)
*)
add "Hello " "World!"
// val it : string = "Hello World!"
add 2 3
// val it : int = 5