Skip to content

Instantly share code, notes, and snippets.

@eiriktsarpalis
Last active March 28, 2016 11:48
Show Gist options
  • Save eiriktsarpalis/0d7378212dd8b4c32c20 to your computer and use it in GitHub Desktop.
Save eiriktsarpalis/0d7378212dd8b4c32c20 to your computer and use it in GitHub Desktop.
Strange issue in F# constraint solving
type Test =
static member F<'T>(t : 'T option) = ()
static member F<'T>(e : 'T -> 'T) = ()
type Foo = { A : int }
type Bar = { A : int }
Test.F(fun (r:Foo) -> { r with A = 32 }) // type checks
Test.F(fun (r:Bar) -> { r with A = 32 }) // type checks
Test.F<Foo>(fun r -> { r with A = 32 }) // type error
Test.F<Bar>(fun r -> { r with A = 32 }) // warning
@drvink
Copy link

drvink commented Feb 24, 2016

F#'s type inference is a bit wonky to begin with, but explicit type arguments make things particularly ugly. Fortunately, there's really only one place you absolutely must use them, the usual case being when you want to parameterize a record, sum, or class by type(s)--but for function calls, as you're doing here, and in fact even when making calls whose overload will be selected via SRTP/constraints rather than simple overloads, you can avoid them entirely:

#light "off"
module Dougu.Show

open System

type Show = class
  static member _resolve = Unchecked.defaultof<Show>

  static member show (x : char) = "'" + Char.ToString x + "'"
  static member show (x : int) = string x
  static member show (x : string) = "\"" + x + "\""

  static member inline invoke x =
    let inline call (_ : ^a, x : ^b) =
      ((^a or ^b) : (static member show : ^b -> string) x) in
    call (Show._resolve, x)
end

let inline show x = Show.invoke x

type Show with
  static member inline show (x : seq<_>) =
    "seq [" + String.concat ", " (Seq.map show x) + "]"
  static member inline show (x : _ list) =
    "[" + String.concat ", " (List.map show x) + "]"
end

Notice the dummy _resolve argument being passed in Show.invoke--and yes, it's stupid that you have to do this at the term level in order to make a type-level mechanism function correctly, but it works, and it avoids the problem you were having. In fact, this technique avoids a much worse problem that often occurs if you attempt to use explicit type parameters instead: the compiler will demand that you affix a constraint signature that it can solve, but which is syntactically invalid and thus impossible to write!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment