I'm trying to create an infix operator to make System.Text.StringBuilder slightly easier to use.
I have the following inline function using statically resolved type parameters:
let inline append value builder = (^T : (member Append : _ -> ^T) (builder, value))
which handles all the overloads of StringBuilder.Append. This works fine as a regular function:
StringBuilder()
|> append 1
|> append " hello "
|> append 2m
|> string
// Result is: '1 hello 2'
When I try to use define an infix operator like so:
let inline (<<) builder value = append value builder
it works when all parameters in a chain are of the same type:
StringBuilder()
<< 1
<< 2
<< 3
|> string
// Result is: '123'
but fails with parameters of different types:
StringBuilder()
<< 1
<< "2" // <- Syntax error, expected type 'int' but got 'string'.
<< 123m // <- Syntax error, expected type 'int' but got 'decimal'.
The expected type seems to be inferred by the first usage of the << operator in the chain. I would assume that each << would be applied separately.
If the chain is split into separate steps the compiler is happy again:
let b0 = StringBuilder()
let b1 = b0 << 1
let b2 = b1 << "2"
let b3 = b2 << 123m
b3 |> string
// Result is: '12123'
Is it possible to create such an operator?
Edit
A hacky "solution" seems to be to pipe intermediate results through the identity function whenever the type of the argument changes:
StringBuilder()
<< 1 // No piping needed here due to same type (int)
<< 2 |> id
<< "A" |> id
<< 123m
|> string
// Result is: '12A123'