【问题标题】:Type inference in sequence expressions in F#F# 中序列表达式中的类型推断
【发布时间】:2013-05-23 12:57:05
【问题描述】:

我想我不太明白 F# 如何在序列表达式中推断类型以及为什么即使我直接从“seq”中指定元素的类型也无法正确识别类型。

在以下 F# 代码中,我们有一个基类 A 和两个派生类 B 和 C:

type A(x) =
    member a.X = x

type B(x) =
    inherit A(x)

type C(x) =
    inherit A(x)

如果我尝试在简单的序列表达式中“生成”它们的实例,我会收到两个错误:

// Doesn't work, but it makes sense.
let testSeq = seq {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2) // Error, expected type: A
}

这是有道理的,因为推断“常见”类型可能不是那么简单(我认为接口可以使这项工作更加困难)。但是,这些错误可以通过安全转换来修复:

// Works fine :)
let testSeqWithCast = seq {
    yield A(0)
    yield B(1) :> A
    yield C(2) :> A
}

如果我不想使用强制转换怎么办?我试图直接从“seq”中指定序列类型,但事情似乎不起作用:

// Should work, I think...
let testGenSeq = seq<A> {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2)
}

所以,我的问题是:有没有办法避免强制转换?如果不是,是否有原因说明即使指定类型也无法使代码正常工作?

我尝试通过以下链接进行挖掘:

http://msdn.microsoft.com/en-us/library/dd233209.aspx http://lorgonblog.wordpress.com/2009/10/25/overview-of-type-inference-in-f/

但我没有发现任何有用的东西......

提前感谢您提供的任何答案:)

【问题讨论】:

    标签: generics types f# inference seq


    【解决方案1】:

    这是一个很好的问题,而且答案可能比您目前得到的答复更复杂。例如,这确实有效:

    let l : A list = [A(0); B(1); C(2)]
    

    但这个看似相似的代码却没有:

    let s : A seq = seq { yield A(0); yield B(1); yield C(2) }
    

    原因其实很微妙。第二种情况脱糖,基本上是一个更复杂的版本:

    let s : A seq = 
        Seq.append (Seq.singleton (A(0))) 
                   (Seq.append (Seq.singleton (B(1))) 
                               (Seq.singleton (C(2)))))
    

    那么问题是什么?最终,问题在于Seq.singleton 具有泛型类型'x -&gt; 'x seq,但我们想在第二次调用中传递B 并返回A seq(通过隐式向上转换实例)。 F# will 将一个 concrete 类型的函数输入隐式向上转换为 concrete 基本类型(例如,如果 Seq.singleton 具有签名 A -&gt; A seq 我们 可以传递B!)。不幸的是,泛型函数不会发生这种情况(泛型、继承和类型推断不能很好地结合在一起)。

    【讨论】:

    • 非常感谢,您的回答很好地了解了问题是如何比看起来更复杂的。一旦我有时间,我将编辑我的问题,并且我将包含您所有答案的“摘要”,以便未来的读者可以快速找到他们想要的内容。再次感谢你:)
    • @Doktor_P 如果您想总结一下,建议您编辑另一个答案或发布您自己的答案。将答案放在问题中会使事情变得混乱。
    【解决方案2】:

    为了了解你的困惑的原因,你不应该比你提到的the link的第一条语句更进一步:

    序列是元素的逻辑系列都是一种类型

    您可以返回只有一个,相同类型的序列,如seq&lt;A&gt;seq&lt;obj&gt;BC 类型是从 A 继承的 OOP 式事实并不相关。以下内容可能会有所帮助:您的所有实例也都继承自 obj,但要从它们中生成 seq&lt;obj&gt;,您应该显式转换:

    // Works fine
    let testSeq = seq<obj> {
        yield A(0) :> obj
        yield B(1) :> obj
        yield C(2) :> obj
    }
    

    或者只是box他们如下:

    // Works fine too
    let testSeq = seq {
        yield box (A(0))
        yield box (B(1))
        yield box (C(2))
    }
    

    编辑:为了理解 F# 中显式转换背后的原因,以下(简单的)考虑可能会有所帮助。类型推断不做猜测;除非它可以确定地派生 seq 类型,或者明确声明它,否则它会报错。

    如果你这样做

    let testSeq = seq {
       yield A(0)
       yield B(1)
       yield C(2)
    }
    

    编译器存在不确定性 - testSeq 可以是 seq&lt;A&gt;seq&lt;obj&gt;,所以它会抱怨。当你这样做时

    let testSeq = seq {
       yield A(0)
       yield upcast B(1)
       yield upcast C(2)
    }
    

    它根据第一个成员的类型将 testSeq 推断为 seq&lt;A&gt; 并将 B 和 C 向上转换为 A 而不会抱怨。同样,如果你这样做

    let testSeq = seq {
       yield box A(0)
       yield upcast B(1)
       yield upcast C(2)
    }
    

    它会根据第一个成员的类型推断testSeqseq&lt;obj&gt;,这次将第二个和第三个成员向上转换为obj,而不是A

    【讨论】:

    • 非常感谢,我忽略了文档中的该声明。我理解你的意思,但是对我来说这仍然是一个很奇怪的决定......事实上,B 和 C 确实有 A 作为类型;事实上,用 C# 编写的等效代码(使用 yield return)可以工作。无论如何,再次感谢您的回答:)
    • 感谢您的编辑,我认为现在情​​况已经很清楚了 :) 在写我对您的答案的第一条评论时,我不情愿地按下回车键并提交了“半写”的答案:我立即完成了,但您可能收到了部分的。对不起!
    • 我已经用一些额外的内容编辑了我的答案,这可能有助于证明 F# 中的明确向上转换规则。
    【解决方案3】:

    在 F# 检查 here 中没有隐式向上转换。您可以尝试推断向上转换。

    let testSeq : seq<A> = seq {
        yield A(0)
        yield upcast B(1)
        yield upcast C(2)
        }
    

    或者,如果足够的话,你可以使用有区别的联合:

    type X =
        | A of int
        | B of int
        | C of int
    
    let testSeq = seq {
        yield A 0
        yield B 1
        yield C 2
        }
    

    【讨论】:

    • 感谢您的回答,但我必须处理的“真实”代码部分是用 C# 编写的。因此,我不能将 A、B、C 的类型“更改”为一个单元,因为在我的代码中它们是用 C# 实现的。无论如何,您的回答提供了一些我可能会真正使用的有用技巧,谢谢!
    【解决方案4】:

    提问者已经接受了答案,但以下内容可能有用。关于“有没有办法避免演员表”的问题,我想补充一下:严格使用seq 答案已经给出(不可能)。

    但是,您可以编写自己的“工作流程”。比如:

      open Microsoft.FSharp.Collections;
    
      let s = seq<string>
    
      type A(x) =
          member a.X = x
    
      type B(x) =
          inherit A(x)
    
      type C(x) =
          inherit A(x)
    
      type MySeq<'a>() =
         member this.Yield(item: 'a): seq<'a> =
            Seq.singleton item
         member this.Yield(item: 'b): seq<'a> =
            Seq.singleton ((item :> obj) :?> 'a)
         member this.Combine(left, right) : seq<'a> =
            Seq.append left right
         member this.Delay (fn: unit -> seq<'a>) = fn()
    
      [<EntryPoint>]
      let main argv = 
    
          let myseq = new MySeq<A>()
          let result = myseq {
            yield A(1)
            yield B(2)
          }
    
          0
    

    请注意,这个答案并不是特别编译时安全,不太确定是否可以这样做(讨厌的通用约束)。

    【讨论】:

      【解决方案5】:

      这只是我的问题收到的所有答案的摘要,以便未来的读者可以通过阅读来节省时间(并决定是否阅读其他答案以获得更好的见解)。

      正如@Gene Belitski 所指出的,对我的问题的简短回答是否定的,在我描述的场景中无法避免强制转换。首先,文档本身声明:

      序列是元素的逻辑系列都是一种类型

      而且,在类似下一种情况下:

      type Base(x) =
          member b.X = x
      
      type Derived1(x) =
          inherit Base(x)
      
      type Derived2(x) =
          inherit Base(x)
      

      我们肯定有Derived1Derived2 的实例也是Base 的实例,但这些实例也是obj 的实例也是事实。因此,在下面的例子中:

      let testSeq = seq {
         yield Base(0)
         yield Derived1(1) // Base or obj?
         yield Derived2(2) // Base or obj?
      }
      

      我们知道,正如@Gene Belitski 所解释的,编译器无法在Baseobj 之间选择正确的祖先。这样的决定可以通过强制转换来帮助,如以下代码所示:

      let testBaseSeq = seq<Base> {
          yield Base(0)
          yield upcast Derived1(1)
          yield upcast Derived2(2)
      }
      
      let testObjSeq = seq<obj> {
          yield Base(0) :> obj
          yield Derived1(1) :> obj
          yield Derived2(2) :> obj
      }
      

      但是,还有更多要解释的。正如@kvb 所说,没有强制转换就无法工作的原因是我们隐含地混合了泛型、继承和类型推断,它们可能无法像预期的那样协同工作。 testSeq 出现的 sn-p 会自动转换为:

      let testSeq = Seq.append (Seq.singleton (Base(0)))
                               (Seq.append (Seq.singleton (Derived1(1)))
                                           (Seq.singleton (Derived2(2))))
      

      问题在于Seq.singleton,需要自动上播(如Seq.singleton (Derived1(1))),但由于Seq.singleton 是通用的,因此无法完成。例如,如果Seq.singleton 的签名是Base -&gt; Base seq,那么一切都会正常进行。

      @Marcus 为我的问题提出了一个解决方案,归结为定义我自己的 sequence builder。我尝试编写以下构建器:

      type gsec<'a>() =
          member x.Yield(item: 'a) = Seq.singleton item
          member x.Combine(left, right) = Seq.append left right
          member x.Delay(fn: unit -> seq<'a>) = fn()
      

      我发布的简单示例似乎运行良好:

      type AnotherType(y) =
          member at.Y = y
      
      let baseSeq = new gsec<Base>()
      let result = baseSeq {
          yield Base(1)        // Ok
          yield Derived1(2)    // Ok
          yield Derived2(3)    // Ok
          yield AnotherType(4) // Error, as it should
          yield 5              // Error, as it should
      }
      

      我还尝试扩展自定义构建器,使其支持更复杂的构造,如 forwhile,但我尝试编写 while 处理程序失败。如果有人感兴趣,这可能是一个有用的研究方向。

      感谢所有回答的人:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 2023-03-25
        • 2013-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多