3

Note: This question is somewhat related to my previous one, but it actually touches the issue from different perspective

Consider following snippet:

let toStr a = a.ToString()
let strOp a = string a

let intToStr = 5 |> toStr
let floatToStr = 5.0 |> toStr

let intStrOp = 5 |> strOp
let floatStrOp = 5.0 |> strOp //type inference error

While the strOp function uses what appears to be more elegant solution, and is able to convert a unit value to string as well, it seems not to be truly generic since it's type gets constrained during it's first usage (even the type inferred is obj -> string, not 'a -> string)

Why doesn't string operator work in such a generic way? Or am I doing something wrong?

Community
  • 1
  • 1
Grzegorz Sławecki
  • 1,727
  • 14
  • 27
  • 2
    `let inline strOp a = string a` – user4003407 Dec 28 '16 at 23:39
  • Is this the famous trick for achievieng higher kinded polymorphism here? If my question is about that, why does the ToString example work? I don't get the difference. – Grzegorz Sławecki Dec 28 '16 at 23:42
  • 5
    Because `toStr` is a true generic function, whereas `strOp` instantiates an inline function with statically resolved type constraints, but is not itself inline, so doesn't carry over the genericity. This may help in explaining the details: http://stackoverflow.com/questions/30445828/f-generics-function-overloading-syntax/30446386#30446386 – Fyodor Soikin Dec 28 '16 at 23:49

1 Answers1

4

The difference is that string uses static member constraints (see the definition) while ToString is an ordinary method available on any object and so the compiler treats the ToString invocation as generic code that does not constrain the instance type in any way.

Static constraints and (non-static) generics work in different ways when they are used in (otherwise unconstrained) let-bound function:

  • For generic code, the compiler propagates the genericity and makes the let-bound function you wrote generic too.

  • For static member constraints, the compiler specializes the code based on the first use. As mentioned in the comments, you can avoid this by using inline, which allows static member-based genericity to propagate in the same way as ordinal generic code.

I think the only reason why the string function uses statically resolved type constraints is that it allows it to specialize as ordinary ToString call for primitive types, but still handle null values in custom way for objects - toStr null throws an exception but strOp null returns an empty string!

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • 4
    I don't think handling nulls is the reason for `string` to have static constraints. If you look at [its definition](https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L4485), those type specializations look rather performance-oriented - i.e. generating​ `call` instead of `callvirt` for known primitive types. And the null check itself is perfectly well performed in [`anyToString`](https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L898) via boxing, without static constraints. – Fyodor Soikin Dec 29 '16 at 01:04