2

I have two functions, horizontal and vertical, for laying out controls. They work like this:

let verticalList = vertical [new TextBlock(Text = "one"); 
                             new TextBlock(Text = "two"); 
                             new TextBlock(Text = "three")]

Now verticalList is a control that displays the three textblocks vertically:

one
two
three

Here are the definitions:

let horizontal controls = 
    let wrap = new WrapPanel() in
    List.iter (wrap.Children.Add >> ignore) controls ;
    wrap

let vertical controls = 
    let stack = new StackPanel() in
    List.iter (stack.Children.Add >> ignore) controls ;
    stack

A problem occurs when I combine different types:

let foo = vertical [new TextBlock(Text = "Title"); vertical items]

This complains that the elements of the list are not of the same type. That is true, but they have a common supertype (UIElement).

I know I can use :> UIElement to upcast both items in the list, but this is an ugly solution. Can F# infer the common supertype. If not, why not?

It would be great if the nice looking

vertical [X; Y; Z]

doesn't have to become

vertical [(X :> UIElement); (Y :> UIElement); (Z :> UIElement)]
Jules
  • 6,089
  • 1
  • 25
  • 40

3 Answers3

5

There are a few ways, including

type Animal() = class end
type Cat() =
    inherit Animal()
type Dog() =
    inherit Animal()
let animals1 : list<Animal> = [upcast new Cat(); upcast new Dog()]
let animals2 = ([upcast new Cat(); upcast new Dog()] : list<Animal>)
let animals3 = [(new Cat() :> Animal); upcast new Dog()]

animals1: type annotation at the var declaration, upcast each element

animals2: type annotation on the list expression, upcast each element

animals3: explicit type on first element, upcast rest

In a future version of F#, the upcast is likely to become unnecessary.

(See also http://cs.hubfs.net/forums/thread/9953.aspx , but nothing 'new' there.)

Brian
  • 113,581
  • 16
  • 227
  • 296
  • Out of curiosity, "future version" as in "final release of .NET 4" or "sometime after that"? – Joel Mueller Jun 01 '09 at 23:11
  • Thanks, that's a better solution! It's great that upcast will become unnecessary. Will the F# compiler be able to infer the type, or do I still need to say `animals : list`? – Jules Jun 01 '09 at 23:54
  • You'll still need "list". (You might also want e.g. list or whatnot. In theory we could try to find greatest common supertype, but with interfaces and whatnot, it seems fine to require the type to be specified expicitly once.) – Brian Jun 02 '09 at 00:15
  • A good principle with type inference is "require the least, provide the most". If you see a function A -> B then you try to find the most general type A and the most specific type B. If you have a list [A,B,C] then you should infer the most specific type (e.g. most derived common superclass of A,B and C). You can handle interfaces by giving the list all interface types implemented by A, B and C. So if A, B and C have common superclass X and implement interfaces I and J then the type is list. Is such a type possible in the current type system? – Jules Jun 02 '09 at 12:33
  • BTW, requiring explicit type annotations is fine of course. I'm happy that you are improving F#. What I've seen so far is great! – Jules Jun 02 '09 at 13:47
2

If you agree to sacrifice type safety for readability here you are the workaround:

open System
let to_list (tuple: Object) = 
    let rec list_after_index (n: int) = 
        let prop = tuple.GetType().GetMethod("get_Item"+n.ToString())
        match prop with
            | null -> []
            | _ -> prop.Invoke(tuple, [||]) :: list_after_index(n+1)
    match tuple with 
        | :? unit -> []
        | _ when tuple.GetType().FullName.Contains(".Tuple`") -> list_after_index(1)
        | _ -> [tuple]

then you can use it like that:

> to_list ();;
val it : obj list = []
> to_list (1);;
val it : obj list = [1]
> to_list([], 1, "2", 3.0);;
val it : obj list = [[]; 1; "2"; 3.0]

e.g. inside your vertical function.

Alexey
  • 8,424
  • 4
  • 55
  • 75
  • I used your code and it doesn't work for long tuples, tuple items after 8 are available from `get_Rest` – Ming-Tang Apr 08 '14 at 01:34
1

I like my previous answer better, but building on that, if you have an app where you're constantly creating non-homogenous lists of animals, you can always do something like this:

let Animalize (x:Animal) = x  // define a function to upcast
let animals4 = [ Animalize <| new Cat(); Animalize <| new Dog()]
// or even
let (~++) = Animalize // define a prefix operator to upcast (~ makes it prefix)
let animals5 = [ ++ new Cat(); ++ new Dog()]

The last is almost certainly an abuse of operators, unless you're in a very highly specialized domain and you constantly need some coercion like this and you're willing to sacrifice inherent readability for terseness.

Brian
  • 113,581
  • 16
  • 227
  • 296