Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# generics / function overloading syntax

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

like image 662
t3dodson Avatar asked May 25 '15 20:05

t3dodson


People also ask

How can I open my old facebook account without password?

Keep in mind that you'll need access to the email associated with your account. Tap Forgot Password?. Type the email, mobile phone number, full name or username associated with your account, then tap Search. Follow the on-screen instructions.


2 Answers

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)
like image 52
Fyodor Soikin Avatar answered Oct 08 '22 10:10

Fyodor Soikin


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

like image 28
FoggyFinder Avatar answered Oct 08 '22 12:10

FoggyFinder