Вопрос

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".

In the following F# code we have a base class A and two derived classes, B and 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:

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

But I found nothing useful...

Thank you in advance for any kind of answer you can give :)

Это было полезно?

Решение 2

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>. The OOP-ish fact that types B and C are inherited from A is not relevant. 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:

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

or just box them like below:

// 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. Type inference does not do guessing; unless it can derive seq type deterministically, or have it explicitly declared, it will complain.

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. 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. 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.

Другие советы

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). F# will implicitly upcast a function input of one concrete type to a concrete base type (e.g. if Seq.singleton had signature A -> A seq we could pass a B!). Unfortunately, this doesn't happen with generic functions (generics, inheritance, and type inference don't play nicely together).

There is no implicit upcasting in F# check here. 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).

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. 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. 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. 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. The snippet in which testSeq appears is automagically transformed in:

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. If the signature of Seq.singleton had been, for example, Base -> Base seq, then everything would have worked.

@Marcus proposes a solution to my question which boils down to define my own sequence builder. 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. That may be a useful research direction, if someone is interested in.

Thanks to everyone who answered :)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top