繁体   English   中英

在F#中的序列表达式中键入推断

[英]Type inference in sequence expressions in F#

我想我不太明白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-in​​ference-in-f/

但我发现没有什么有用的......

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

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

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 -> 'x seq ,但我们想要传递B并在第二次调用中返回A seq (通过隐式地向上转换实例)。 F# 隐式地一个具体类型的函数输入向上转换为具体的基类型(例如,如果Seq.singleton具有签名A -> A seq我们可以传递B !)。 不幸的是,通用函数不会发生这种情况(泛型,继承和类型推断不能很好地协同工作)。

为了解您混淆的原因,您不应该进一步,而不是您提到的链接的第一个声明:

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

您可以返回仅一个序列,相同类型,如seq<A>seq<obj> 事实上,类型BC是从A继承的, A是不相关的。 以下内容可能有所帮助:您的所有实例也都是从obj继承的,但是为了从它们中获取seq<obj>您应该显式地转换:

// 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<A> ,也可以是seq<obj> ,所以它会抱怨。 当你这样做

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

它根据第一个成员的类型将testSeq推断为seq<A> ,并且A没有抱怨A情况下将B和C向上转换为A 同样,如果你这样做

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

它会根据第一个成员的类型将testSeq推断为seq<obj> ,这次将第二个和第三个成员向上转换为obj ,而不是A

有一个在F#检查没有隐含上溯造型在这里 您可以尝试推断上传。

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
    }

提问者已经接受了答案,但以下内容可能有用。 关于“有没有办法避免强制转换”的问题,我想补充一点:严格使用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

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

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

正如@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出现的片段自动转换为:

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 (Derived1(1)) ),但由于Seq.singleton是通用的,因此无法完成。 如果Seq.singleton的签名例如是Base -> Base seq ,那么一切都会有效。

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

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处理程序失败了。 如果有人感兴趣,这可能是一个有用的研究方向。

谢谢大家回答:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM