为什么在文件之间移动代码时类型会发生变化

Why do the types change when I move code between files

我有两个文件。第一个文件 RailwayCombinator.fs 的内容是:

    module RailwayCombinator

    let (|Uncarbonated|Carbonated|) =
        function 
        | Choice1Of2 s -> Uncarbonated s
        | Choice2Of2 f -> Carbonated f

    let uncarbonated x = Choice1Of2 x
    let carbonated x = Choice2Of2 x

    let either successFunc failureFunc twoTrackInput =
        match twoTrackInput with
        | Uncarbonated s -> successFunc s
        | Carbonated f -> failureFunc f

第二个文件 Program.fs 的内容是:

    open RailwayCombinator

    let carbonate factor label i = 
        if i % factor = 0 then
        carbonated label
        else
        uncarbonated i

    let fizzBuzz = 
        let carbonateAll = 
        carbonate 3 "Fizz" <+> carbonate 5 "Buzz"

        carbonateAll 

我也有代码块:

let (<+>) switch1 switch2 x = 
    match (switch1 x),(switch2 x) with
    | Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
    | Uncarbonated f1,Carbonated s2  -> carbonated s2
    | Carbonated s1,Uncarbonated f2 -> carbonated s1
    | Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1

如果我将代码块放在名为 Program 的文件中,它就可以正常编译。相反,如果我把它放在 RailwayCombinator 中,我会在这一行收到错误。

carbonate 3 "Fizz" <+> carbonate 5 "Buzz"

错误是:

This expression was expected to have type
    int    
but here has type
    string  

我还注意到 <+> 的签名根据它所在的文件而改变,但我不知道为什么签名会改变。在 RailwayCombinator 中时的签名是:

val ( <+> ) :
  switch1:('a -> Choice<'b,int>) ->
    switch2:('a -> Choice<'c,int>) -> x:'a -> Choice<'b,int>

在程序中时,签名更改为

val ( <+> ) :
  switch1:('a -> Choice<'b,string>) ->
    switch2:('a -> Choice<'c,string>) -> x:'a -> Choice<'b,string>

为什么签名会改变?

<+> 组合子的实现使用 + 运算符。 F# 编译器不知道如何生成此泛型(.NET 泛型没有表明类型应为 "anything with + operator" 的泛型约束)。因此,F# 编译器会根据使用运算符的定义下方的第一段代码选择 <+> 的第一种类型。

您可以通过内联定义来解决这个问题:

let inline (<+>) switch1 switch2 x = 
    match (switch1 x),(switch2 x) with
    | Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
    | Uncarbonated f1,Carbonated s2  -> carbonated s2
    | Carbonated s1,Uncarbonated f2 -> carbonated s1
    | Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1

inline 由 F# 编译器直接处理,因此它们支持更强大的通用约束 - 包括表示 "anything with +".

的约束