views:

311

answers:

4

I have a curried function that I'd like it to support different types of parameters, that are not on a inheritance relationship:

type MyType1 = A | B of float
type MyType2 = C | D of int

What I tried to do is:

let func x y =
    match (x, y) with
    | :? Tuple<MyType1, MyType1> -> "1, 1"
    | _ -> "..."

However this is not possible. F# complains:

The type ''a * 'b' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.

What is an elegant way to do this?

EDIT: Let me try to clarify this.

I have two similar, but distinct, types. I can very easily convert one type to another. I want to define a binary operation that will act on entities of those types, but I'd like to expose a single operation to the client.

That is, instead of providing:

let op11 (x : MyType1) (y : MyType1) = // do something useful
let op12 (x : MyType1) (y : MyType2) =
    // convert y to MyType1
    let y' = // ...
    // dispatch to op11
    op11 x y'
let op21 (x : MyType2) (y : MyType1) = // similar
let op22 (x : MyType2) (y : MyType2) = // similar

what I would like is to expose a single function to client code:

let op (x : obj) (y : obj) = // ...

This is like simulating the behavior of method overloading, but with curried functions.

+3  A: 

Your code doesn't work, because F# generalizes the type of arguments to a type parameter. I think you can't dynamically test whether a type 'a * 'b can be converted to type MyType1 * MyType2 (though this is a bit confusing to me). In any case, you can write a function that takes two arguments of type obj and tests them separately using two :? patterns:

type MyType1 = A | B of float 
type MyType2 = C | D of int

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
        printfn "%A %A" x1 x2
    | _ -> 
        printfn "something else" 

func A (B 3.0) // A B 3.0
func A (D 42)  // something else

Anyway, it would be interesting to know why do you want to do this? There may be a better solution...

EDIT (2) So, from all 4 two-element combinations of T1 and T2, you want the function that can take 3. Is that correct (T1 * T1, T1 * T2 and T2 * T2)? In that case, you can't write a fully safe curried function, because the type of second argument would "depend" on the type of frist argument (if the first argument has a type T2, then the second argument also has to be T2 (otherwise it can be T1 too)).

You can write a safe non-curried function that takes an argument of the following type:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2

The type of the function would be MyArg -> string. If you want a curried function, you can define a type which allows you to use either T1 or T2 as both first and second argument.

type MyArg = First of T1 | Second of T2

Then, your curried function will be MyArg -> MyArg -> string. But note that if one combination of argument types is not allowed (if I understand you correctly, T2 * T1 shouldn't be allowed). In this case, your function will simply have to throw an exception or something like that.

Tomas Petricek
Excelent! That is what I was looking for, a way to use two `:?` patterns. Thanks.
Bruno Reis
Why: simplifying a lot, I have this: `type data = int`, `type MyType = A | B of data | C of data * data`. I want a `makeC : ??? -> ??? -> MyType`, that will take either 2 `data`s, or a `data` and a `B(x)`, or two `B(x)`s.
Bruno Reis
Regarding 'makeC', you don't really want that function.
Brian
Tomas, what if I want that function you mention in your edit to be a curried function?
Bruno Reis
@Bruno: I did one more edit of the answer.
Tomas Petricek
+1  A: 

This smells very fishy, you should describe the bigger problem context as it seems like you shouldn't be in this situation. That said:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B
let DoIt x y =
    match x, y with
    | A, A -> 0.0
    | A, B z -> z
    | B z, A -> z
    | B z1, B z2 -> z1 + z2

let Convert x =
    match x with
    | C -> A
    | D i -> B (float i)

let Func (x:obj) (y:obj) =
    match x, y with
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy)
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)    
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy    
    | _ -> failwith "bad args"
Brian
Bigger context: I'm dealing with "point sets", that might be empty, single points, lines or unions of all those: `type PSet = Empty | Single of point | Line of point * direction | Union of PSet list`. I want to be able to make a line from 2 points, they might be given either as two `points` or wrapped inside a `Single(point)`.
Bruno Reis
Right. Your 'makeLine' function should probably take two points, and clients that have 'Single's should 'unwrap' them before calling 'makeLine'. Or possibly use overloading, or have a separate function (e.g. 'makeLineFromSingles'). Don't do what you're doing, you're throwing away static type safety, and making the code needlessly complicated. From an OO background, it's tempting to make a bunch of overloads that 'massage' somewhat similar data to squish it into the right shape. Fight that tendency and you'll wind up better off.
Brian
Lol. Yeah, you are right, I'm thinking in OO (as I've been doing for maaaaany years) too much. It might be healthy to learn to do things in other ways!
Bruno Reis
Note that 'unwrapping' a 'Single' should be dirt-easy, in fact probably already done by any client, since any time a client is using a piece of PSet data, it is doing a 'match' and so 'Single p' would be one of the cases and voila, you have 'p' as the unwrapped point right there.
Brian
+2  A: 

I have two similar, but distinct, types. I can very easily convert one type to another. I want to define a binary operation that will act on entities of those types, but I'd like to expose a single operation to the client.

This function should decide which of the opXY to call, properly downcasting types.

This is one of those cases where the correct answer isn't "here's how you do it", but instead "don't do it like that". There's no real advantage using F# if you aren't going to stick with its idioms and type-checker.

From my point of view, if your types are so similar, they should be merged into the same type:

type MyType = A | B of float | C | D of int

Barring that, you can wrap your two types in another type:

type composite =
    | MyType1Tuple of MyType1 * MyType1
    | MyType2Tuple of MyType2 * MyType2

Another layer of indirection never hurt anyone. But at least now your clients can wrap the objects with another without losing type safety.

And barring all of that, expose two separate methods for your different types.

Juliet
+5  A: 

There are essentially three different ways to accomplish this.

The first is to sacrifice static typing by upcasting to obj as you suggested:

let func x y =
  match box x, box y with
  | (:? MyType1 as x), (:? MyType1 as y) ->
      ...

This is virtually always an awful solution because it results in the introduction of unnecessary run-time type checks:

  | _ -> invalidArg "x" "Run-time type error"

The only place I have seen this work well is library code designed specifically for users to invoke from F# interactive sessions where compile-time and run-time type errors effectively occur at the same time and dynamic typing can be more concise. For example, our F# for Visualization library allows the user to try to visualize any value of any type using this technique to invoke custom visualization routines for different (known) types (read more).

The second is to replace your two separate types with a single type:

type MyType1 = A | B of float | C | D of int   

The third solution is to introduce a new type that unifies just those two types:

type MyType = MyType1 of MyType1 | MyType2 of MyType2
Jon Harrop