4
votes

Given a type with with a type parameter is it possible to then create a list containing values with mixed concrete types? See the code below. All works well until I try to create list2 where some elements have an int for data and others have a float. Is there a way to do this? My attempt in list3 does not compile.

type Holder<'data> = {
    Data : 'data
    Name : String
}

let intVal =
    {Data = 23;
    Name = "Bill"}

let intVal2 =
    {Data = 29;
    Name = "Cindy"}

let floatVal =
    {Data = 23.0;
    Name = "John"}

let list1 = [intVal; intVal2]
let list2 = [intVal; floatVal]
let list3 : Holder<'a> list = [intVal; floatVal]
1
No -- the typing system in F# is stronger than C#. You'll have to use [float intVal; floatVal].Rob Lyndon

1 Answers

6
votes

When creating a list, the elements of the list have to be of the same type. This means that you cannot create a list containing Holder<int> and Holder<float>.

If you want to create a list of mixed type, you can either define a discriminated union:

type Value = 
  | Float of Holder<float>
  | Int of Holder<int>

And create a list of type Value using:

let list = [Int intVal; Float floatVal]

Or you can define an interface that captures the commonality between the two (here, the shared Name property) and create a list of values of the interface type. In your example this would look as follows:

type IHolder = 
  abstract Name : string

type Holder<'data> = 
  { Data : 'data
    Name : string }
  interface IHolder with
    member x.Name = x.Name

Create a list of Holder<_> values converted to IHolder instances:

let list = [intVal :> IHolder; floatVal :> IHolder]
for h in list do printfn "%s" h.Name

The interface option lets you easily print all names, but it makes it hard to access the data. With discriminated union, the types are more complex, but you can pattern match on it to get the values.