0

This code doesn't compile:

let f = fun x y -> x <<< y // bit shift
let g = fun x y -> x <<< y

[<EntryPoint>]
let main _ =
  printfn "%d" <| f 1 10
  printfn "%d" <| f 1L 10 // error
  printfn "%d" <| g 1L 10
  0
(7,21): error FS0001: This expression was expected to have type
    int
but here has type
    int64

I guess the unifier fixed the type parameters associated with f and g upon seeing their first occurrences. What governs this process? I think this is very similar to "value restriction" but f and g are already eta-expanded! This is a hard problem.

I would surely imagine there's some black magic behind typing predefined operators with ad-hoc polymorphism over integral types, but that's just my speculation. Any information appreciated.

nodakai
  • 7,773
  • 3
  • 30
  • 60

2 Answers2

2

Generic numeric programming is done using static member constraints, which can't be represented in the .NET type system. They exist only in F# and therefore must be marked inline.

Your code could be written this way:

let inline f x y = x <<< y // bit shift
let inline g z y = z <<< y

[<EntryPoint>]
let main _ =
  printfn "%d" <| f 1 10
  printfn "%d" <| f 1L 10 // works too
  printfn "%d" <| g 1L 10
  0

More info on MSDN:
Inline Functions
Statically Resolved Type Parameters

Daniel
  • 47,404
  • 11
  • 101
  • 179
  • I wonder how long it could have took me to find these pages by myself! – nodakai Oct 29 '14 at 15:16
  • I would like to expand on this answer, which has actually two parts: 1) Defining a syntactic function instead of a right-hand-side lambda function solves the value restriction problem: `let f x y = x <<< y`, and 2) the `inline` solves the operator constraint problem. B.t.w., `System.Int32` does not really have a static member `op_LeftShift`. The F# compiler implicitly augments the type with it, which further complicates type inference. – Marc Sigrist Oct 29 '14 at 15:40
  • @MarcSigrist I think `let f x y = x <<< y` is just a syntax sugar for `let f = fun x y -> x <<< y`. I was just desperate in getting my code compile, so I used the most verbose form trying to get any possible clues. It should be compared against the partial application form `let f = (<<<)` and this one should be vulnerable to value restriction. – nodakai Oct 29 '14 at 16:03
  • @nodakai: There is a meaningful distinction in F# between _syntactic functions_ and _function values_. Only syntactic functions can be inlined. – Daniel Oct 29 '14 at 16:44
  • @Daniel Could you point me towards some in-depth explanation of the difference between syntactic functions and function values? – Asik Oct 30 '14 at 20:50
  • I would probably start with [_Automatic Generalization_](http://msdn.microsoft.com/en-us/library/dd233183.aspx) (especially the section on value restriction) on MSDN. This [blog post](http://blogs.msdn.com/b/mulambda/archive/2010/05/01/value-restriction-in-f.aspx) on "the finer points" is also very good. The essence of F#'s behavior is captured by this sentence in the first article: _The compiler performs automatic generalization only on complete function definitions that have explicit arguments, and on simple immutable values._ – Daniel Oct 31 '14 at 13:52
1

I think it is how F# performs automatic generalization of function parameters. On first appearance it infers that the function 'f' could have type ('a -> 'a -> 'a) but the second appearance is not match this signature because it has a different signature ('b -> 'a -> 'a) because it consider int64 and int as different types.

Inlining functions could sometimes solve this problem as @Daniel mentioned

Slightly more info could be found here: http://msdn.microsoft.com/en-us/library/dd233183.aspx

Some more info on static member constraints could be found in this post by Tomas Petricek: http://tomasp.net/blog/fsharp-generic-numeric.aspx/

Petr
  • 4,280
  • 1
  • 19
  • 15
  • "On first appearance it infers that the function `f` could have type `('a -> 'a -> 'a)`" I don't think that is how HM works. HM infers the most general type of a function (a term, in general) just by looking at its definition (body.) (In this case `^a -> int32 -> ^a when ...`) – nodakai Oct 29 '14 at 15:18
  • Note the difference between my problem and `max 2.0 3` in the MSDN page you quoted. My `f` and `g` are essentially identical, so when the first and the third line of the body of my `main` function compile fine, there shouldn't be any reason for the second line not to compile (unless... complications like "static method" are involved.) OTOH the problem with `max 2.0 3` is simply that the definition of `max` itself was not general enough to support such a usage. – nodakai Oct 29 '14 at 15:28
  • I'll make time to read through Tomas' article. Thank you very much for the valuable information! – nodakai Oct 29 '14 at 15:33