简体   繁体   English

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

[英]Type inference in sequence expressions in F#

I think I do not quite understand how F# infers types in sequence expressions and why types are not correctly recognized even if I specify the type of the elements directly from "seq". 我想我不太明白F#如何推断序列表达式中的类型以及为什么类型不能正确识别,即使我直接从“seq”指定了元素的类型。

In the following F# code we have a base class A and two derived classes, B and C: 在下面的F#代码中,我们有一个基类A和两个派生类,B和C:

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

type B(x) =
    inherit A(x)

type C(x) =
    inherit A(x)

If I try to "yield" their instances in a simple sequence expressions, I get two errors: 如果我尝试用简单的序列表达式“产生”它们的实例,我会得到两个错误:

// 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
}

That can make sense, since it may not be so trivial to infer "common" types (interfaces, I think, can make that work far harder). 这是有道理的,因为推断“常见”类型(界面,我认为,可以使这项工作更加困难)可能并不那么简单。 However, those errors can be fixed with a safe cast: 但是,可以使用安全转换来修复这些错误:

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

What if I do not want to use casts? 如果我不想使用演员怎么办? I tried to specify the sequence type directly from "seq", but things do not seem to work: 我试图直接从“seq”指定序列类型,但事情似乎不起作用:

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

So, my question is: is there a way to avoid casts? 所以,我的问题是:有没有办法避免演员表? If not, is there a reason why even specifying the type doesn't make the code work? 如果没有,是否有一个原因,即使指定类型不会使代码工作?

I tried digging through following links: 我尝试通过以下链接挖掘:

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

But I found nothing useful... 但我发现没有什么有用的......

Thank you in advance for any kind of answer you can give :) 提前感谢您提出的任何答案:)

This is a good question, and the answer is probably more complicated than the responses you've gotten so far indicate. 这是一个很好的问题,答案可能比你到目前为止所得到的答案更复杂。 For instance, this does work: 例如,这确实有效:

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

but this seemingly analogous code doesn't: 但这个看似类似的代码不会:

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

The reason is actually very subtle. 原因其实非常微妙。 The second case desugars to something which is basically a more complicated version of: 第二种情况涉及到基本上更复杂的版本:

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

So what's the problem? 所以有什么问题? Ultimately, the problem is that Seq.singleton has generic type 'x -> 'x seq , but we want to pass a B and get back an A seq in the second call (by implicitly upcasting the instance). 最终,问题是Seq.singleton具有泛型类型'x -> 'x seq ,但我们想要传递B并在第二次调用中返回A seq (通过隐式地向上转换实例)。 F# will implicitly upcast a function input of one concrete type to a concrete base type (eg if Seq.singleton had signature A -> A seq we could pass a B !). F# 隐式地一个具体类型的函数输入向上转换为具体的基类型(例如,如果Seq.singleton具有签名A -> A seq我们可以传递B !)。 Unfortunately, this doesn't happen with generic functions (generics, inheritance, and type inference don't play nicely together). 不幸的是,通用函数不会发生这种情况(泛型,继承和类型推断不能很好地协同工作)。

In order to understand the cause of your confusion you should not go anywhere further, than the first statement of the link you referred to : 为了解您混淆的原因,您不应该进一步,而不是您提到的链接的第一个声明:

A sequence is a logical series of elements all of one type . 序列是一种类型的逻辑系列元素。

You can return a sequence of only one, the same type like seq<A> , or seq<obj> . 您可以返回仅一个序列,相同类型,如seq<A>seq<obj> The OOP-ish fact that types B and C are inherited from A is not relevant. 事实上,类型BC是从A继承的, A是不相关的。 The following may help: all your instances are also inherited from obj , but in order to make from them a seq<obj> you should explicitly cast: 以下内容可能有所帮助:您的所有实例也都是从obj继承的,但是为了从它们中获取seq<obj>您应该显式地转换:

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

or just box them like below: 或者只是将它们box如下:

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

EDIT : For understanding the reasoning behind explicit casting in F# the following (simplistic) consideration may help. 编辑 :为了理解F#中显式转换背后的原因,以下(简单化)考虑可能会有所帮助。 Type inference does not do guessing; 类型推断不做猜测; unless it can derive seq type deterministically, or have it explicitly declared, it will complain. 除非它可以确定性地推导出seq类型,或者明确声明它,否则它会抱怨。

If you just do 如果你这样做

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

compiler is presented with indeterminism - testSeq can be either seq<A> , or seq<obj> , so it complains. 编译器带有不确定性 - testSeq可以是seq<A> ,也可以是seq<obj> ,所以它会抱怨。 When you do 当你这样做

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

it infers testSeq as seq<A> based on type of the first member and upcasts B and C to A without complaining. 它根据第一个成员的类型将testSeq推断为seq<A> ,并且A没有抱怨A情况下将B和C向上转换为A Similarly, if you do 同样,如果你这样做

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

it will infer testSeq as seq<obj> based on the type of the first member upcasting this time second and third members to obj , not A . 它会根据第一个成员的类型将testSeq推断为seq<obj> ,这次将第二个和第三个成员向上转换为obj ,而不是A

There is no implicit upcasting in F# check here . 有一个在F#检查没有隐含上溯造型在这里 You can try inferred upcasting. 您可以尝试推断上传。

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

Or if it is enough you can use discriminated unions: 或者,如果它足够,您可以使用受歧视的联盟:

type X =
    | A of int
    | B of int
    | C of int

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

The asker has already accepted an answer, however the following may be useful. 提问者已经接受了答案,但以下内容可能有用。 On the issue of "is there a way to avoid casts" I would like to add: using strictly seq the answer is as already given (not possible). 关于“有没有办法避免强制转换”的问题,我想补充一点:严格使用seq ,答案就像已经给出的那样(不可能)。

However you could write your own "workflow". 但是,您可以编写自己的“工作流程”。 Something like: 就像是:

  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

Note that this answer is not particularly compile time safe, not quite sure if that can be done (pesky generic constraints). 请注意,这个答案并不是特别编译时安全,不太确定是否可以完成(烦人的通用约束)。

This is just a summary of all answers my question has received, so that future readers can save their time by reading this (and decide whether or not reading other answers to get a better insight). 这只是我的问题收到的所有答案的摘要,以便未来的读者可以通过阅读(并决定是否阅读其他答案以获得更好的洞察力)来节省时间。

The short answer to my question, as pointed out by @Gene Belitski, is no, it's not possible to avoid casts in the scenario I described. 正如@Gene Belitski所指出的那样,对我的问题的简短回答是否定的,在我描述的场景中不可能避免演员表。 First of all, the documentation itself states that: 首先,文档本身表明:

A sequence is a logical series of elements all of one type . 序列是一种类型的逻辑系列元素。

Moreover, in a situation like the next one: 而且,在下一个情况下:

type Base(x) =
    member b.X = x

type Derived1(x) =
    inherit Base(x)

type Derived2(x) =
    inherit Base(x)

We surely have that an instance of Derived1 or of Derived2 is also an instance of Base , but it is also true that those instances are also instances of obj . 我们肯定有Derived1Derived2的实例也是Base的实例,但这些实例也是obj实例也是如此。 Therefore, in the following example: 因此,在以下示例中:

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

We have that, as explained by @Gene Belitski, the compiler cannot choose the right ancestor between Base and obj . 我们有,正如@Gene Belitski所解释的那样,编译器无法在Baseobj之间选择正确的祖先。 Such decision can be helped with casts, as in the following code: 这些决定可以通过强制转换来帮助,如下面的代码所示:

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
}

However, there's more to explain. 但是,还有更多要解释的内容。 As @kvb states, the reason this can't work without casts is that we are implicitly mixing generics, inheritance, and type inference, which may not work together as well as expected. 正如@kvb所说的那样,没有强制转换就无法工作的原因是我们隐含地混合了泛型,继承和类型推断,它们可能不会像预期的那样协同工作。 The snippet in which testSeq appears is automagically transformed in: testSeq出现的片段自动转换为:

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

The issue lies in Seq.singleton , where an automatic upcast would be needed (like in Seq.singleton (Derived1(1)) ) but it cannot be done since Seq.singleton is generic. 问题在于Seq.singleton ,需要自动向上 Seq.singleton (Derived1(1)) (如Seq.singleton (Derived1(1)) ),但由于Seq.singleton是通用的,因此无法完成。 If the signature of Seq.singleton had been, for example, Base -> Base seq , then everything would have worked. 如果Seq.singleton的签名例如是Base -> Base seq ,那么一切都会有效。

@Marcus proposes a solution to my question which boils down to define my own sequence builder . @Marcus提出了我的问题的解决方案,归结为定义我自己的序列构建器 I tried writing the following 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()

And the simple example I posted seems to be working fine: 我发布的简单示例似乎工作正常:

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
}

I also tried extending the custom builder so that it supported more complex constructs like for and while , but I failed trying writing the while handler. 我也尝试扩展自定义构建器,以便它支持更复杂的构造,如forwhile ,但我尝试编写while处理程序失败了。 That may be a useful research direction, if someone is interested in. 如果有人感兴趣,这可能是一个有用的研究方向。

Thanks to everyone who answered :) 谢谢大家回答:)

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

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