Description
Today, I encountered a strange compiler error while working with operators that use SRTPs. It can be best explained by the following snippet I created:
type MyString = MyString of string
with
member x.Append(s) =
let (MyString x) = x
x + s |> MyString
member x.Append(d) = x.Append(sprintf "%d" d)
member x.FinalAppend(s: string) =
let (MyString x) = x.Append(s)
x
type MyOtherString = MyOtherString of string
with
member x.Append(s) =
let (MyOtherString x) = x
x + s |> MyOtherString
member x.Append(d) = x.Append(sprintf "%f" d)
member x.FinalAppend(s: string) =
let (MyOtherString x) = x.Append(s)
x
// This function is generic.
// It performs ms.Append(x),
// for any two values whose
// types have this member.
let inline (.>>) ms x = (^TMyString : (member Append: ^TMyOperand -> ^TMyString) (ms, x))
// These functions are _not_ generic.
let inline (=%) (ms: MyString) x = ms.FinalAppend x
let inline (=@) (ms: MyOtherString) x = ms.FinalAppend x
// These examples work.
// The type at the left of .>> is always the same.
let works1 = MyString "This" .>> " chain" .>> 6 =% " works."
let works2 = MyOtherString "Working" .>> " as" .>> 6.0 =@ " well."
// Removing the final operator
// however causes errors as soon
// as we use a type different than
// the string we were using until now...
let notworks = MyString "Doesn't" .>> " work." .>> 6
At the final example, we omit the =%
operator and it should return the MyString
object directly. However, the compiler expects the 6
to have been a string, instead of an int.
At the first example, the =%
operator at the end makes sure that the left operand is a MyString
, the right operand is a string, and the result is a string as well.
The question is: Didn't the compiler already know that already? Let's take a look at his deductive process:
MyString "Doesn't"
is surely of typeMyString
.MyString "Doesn't" .>> " work"
must be aMyString
, from the definition of.>>
.MyString "Doesn't" .>> " work." .>> 6
, is again aMyString
. The final.>>
should resolve toMyString.Append(int)
, but our compiler falsely resolves it toMyString.Append(string)
instead.
Using .>>
like that does not seem to be prohibited; it works above, when we used the =%
operator, and made the compiler feel safe that he was dealing with an object of type MyString
.
I made a couple of theories about this bug:
-
In
^TMyString : (member Append: ^TMyOperand -> ^TMyString)
, don't these^TMyString
s refer to the same type? Doesn't that guarantee that the method's return type is the same with the type the method resides? -
let x = MyString "Doesn't" .>> " work" in x .>> 6
works, which led me to wonder if the compiler mistakenly believes that.>>
's^TMyOperand
must remain the same in the entirelet
block. But this does does not hold, since there is no problem when we added=%
.