I'm guessing you want something like this?

1
2
3
4
5
let toB = function A x -> B (float x) | x -> x
let toA = function B x -> A (int x) | x -> x

let allA = Aonly |> List.map toA
let allB = Aonly |> List.map toB
By on 2/6/2013 1:41 PM ()

Let me explain the problem in an other way.

1
2
3
4
5
6
7
8
9
10
11
// If we extend the DU, there are more possibilities/directions to transform: AtoB, CtoA, ..
type ABC =
    | A of int
    | B of float
    | C of decimal

// So the transformations should be parametrized, like this
let XtoY X Y f = function X n -> Y (f n) | n -> n   // not working

// I can't get the X and Y functions as parameters.

Note: This example is choosen to have an unwrapper X and a wrapper Y that will be needed as parameter.
I will not use it on such a "ABC" type and convert numeric types.

By on 2/6/2013 2:10 PM ()

It is actually possible to use reflection to implement this. However, this is very hacky and probably very inefficient too. I write it here because it's fun to see in action, but you should not use it in anything remotely serious.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
open Microsoft.FSharp.Reflection

let XtoY (X: 'a -> 't) (Y: 'b -> 't) (f: 'a -> 'b) =
  let xInfo, _ = FSharpValue.GetUnionFields(X Unchecked.defaultof<'a>, typeof<'t>)
  function n ->
    let nInfo, nValues = FSharpValue.GetUnionFields(n, typeof<'t>)
    if xInfo.Tag = nInfo.Tag then
      Y (f (nValues.[0] :?> 'a))
    else
      n

// example use.

type ABC =
  | A of int
  | B of float
  | C of decimal

let AtoB = XtoY A B float
AtoB (A 12) // = B 12.

Also, it doesn't work if A takes multiple values (ex. A of int * int), because in this case Unchecked.defaultof returns null, and multiple-value constructors throw an exception when passed null.

EDIT: For the sake of playing with reflection, here is a version that works with a multiple-valued constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
open Microsoft.FSharp.Reflection

let XtoY (X: 'a -> 't) (Y: 'b -> 't) (f: 'a -> 'b) =
    let dummy =
        if FSharpType.IsTuple typeof<'a> then
            let tupleTypes = FSharpType.GetTupleElements typeof<'a>
            let tupleValues = Array.zeroCreate<obj> tupleTypes.Length
            FSharpValue.MakeTuple(tupleValues, typeof<'a>) :?> 'a
        else Unchecked.defaultof<'a>
        |> X
    let xInfo, _ = FSharpValue.GetUnionFields(dummy, typeof<'t>)
    fun n ->
        let nInfo, nValues = FSharpValue.GetUnionFields(n, typeof<'t>)
        if xInfo.Tag = nInfo.Tag then
            let a =
                if nValues.Length = 1 then
                    nValues.[0] :?> 'a
                else
                    FSharpValue.MakeTuple(nValues, typeof<'a>) :?> 'a
            Y (f a)
        else
            n

type AB =
    | A of int * float
    | B of int * int

let AtoB = XtoY A B (fun (x, y) -> x, x)

AtoB (A (32, 2.)) // = B (32, 32)
By on 2/6/2013 4:33 PM ()

It seems to be not possible without Reflection.
I marked it as answer.

By on 2/7/2013 4:26 AM ()

Ah, I think I understand better now. The problem is that A, B, C are constructors, i.e. functions int -> ABC, float -> ABC, etc.; but what you need here are functions that tell you if some instance of ABC is an A or not, etc. So:

1
2
3
4
5
6
7
8
let isA = function A x -> Some x | _ -> None
let isB = function B x -> Some x | _ -> None
let isC = function C x -> Some x | _ -> None
 
let XtoY X Y f a = match X a with Some n -> Y (f n) | _ -> a

let AtoB = XtoY isA B // (int -> float) -> ABC -> ABC
let BtoC = XtoY isB C // (float -> decimal) -> ABC -> ABC
By on 2/6/2013 2:59 PM ()

Yeah, that, but I don't want to write 3 functions like isA, isB and isC (DRY!),
I want also to parametrize this isX function, to be fully parametric.
And that's the hard part. Because in isX, the constructor is used as a pattern matcher, and I didn't found a way to parametrize that.

By on 2/6/2013 3:22 PM ()

Unfortunately, the F# compiler does not generate these "extractor" functions automatically. This is similar to a getter in a partial lens, and in Scala they're using macros to generate this boilerplate; in Haskell they're using Template Haskell AFAIK.

Not sure what you mean by "fully parametric". As I defined it, XtoY is parametric, for example the parameter X used to extract a value is of type 'a -> 'b option

By on 2/6/2013 3:46 PM ()

Thank you for your fast responses!!

Ahh that's a shame : "Unfortunately, the F# compiler does not generate these "extractor" functions automatically."

This one is some kind related: [link:stackoverflow.com]

Where to find more information about that?
Have you found a workaround for the "partial lenses"?

By on 2/6/2013 3:58 PM ()

If the only thing you need now is a parameterized function to tell you if an object is of some union case X in ABC, all you need to do is type test. Internally, union cases are implemented as distinct classes (per specification). Identical union cases will always be of the same type, and differing union case will be of differing types. I think there are some exceptions to this, but they are, well, exceptional.

You cannot access the types directly (at least, not normally -- you could acquire them using reflection). However, you only need to get an instance for this to work.

1
2
3
4
5
6
7
type ABC = 
| A of int
| B of int
let isX (abc1 : ABC) (abc2 : ABC) = abc1.GetType() = abc2.GetType()
let t1,t2,t3 = isX (A 1) (A 5), isX (A 1) (B 5), isX (B 5) (A 5) 
//This will give true,false,false

BTW, the tester properties IsA, IsB, IsC actually exist. Per specification, the compiler generates static factory methods, testers, and some other things for interop with CLI.

They are invisible to F#, however. Member constraints will also fail to recognize them. However, you can see them if you enable 'Show raw object properties' in the Debug options or if you use reflection. I think it will also work if you use late binding but I'm not sure.

By on 2/13/2013 4:47 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper