Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# pattern matching on types of tuples

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.

like image 548
Bruno Reis Avatar asked Feb 06 '10 18:02

Bruno Reis


Video Answer


1 Answers

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

like image 137
Tomas Petricek Avatar answered Oct 29 '22 16:10

Tomas Petricek