views:

413

answers:

3

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)]
+4  A: 

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
Out of curiosity, "future version" as in "final release of .NET 4" or "sometime after that"?
Joel Mueller
Probably in Beta2.
Brian
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<Animal>`?
Jules
You'll still need "list<Animal>". (You might also want e.g. list<obj> 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
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<X and implements I and implements J>. Is such a type possible in the current type system?
Jules
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
+1  A: 

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
+2  A: 

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