开发者

Conversion between types in discriminated unions

开发者 https://www.devze.com 2023-02-17 02:42 出处:网络
I have a function, which can returns different types, and I use discriminated union for this. What I need, is to have conversion from one type in discriminated union to another type.

I have a function, which can returns different types, and I use discriminated union for this. What I need, is to have conversion from one type in discriminated union to another type. Also some of the types can be convertable to all other types (String), but some of the types can be converted only to String (MyCustomType开发者_如何学Go)

For this I've added member method ConvertTo to the ResultType:

type MyTypes = 
   | Boolean       = 1
   | Integer       = 2
   | Decimal       = 3
   | Double        = 4
   | String        = 5
   | MyCustomType  = 6

type ResultType = 
   | Boolean of bool
   | Integer of int
   | Decimal of decimal
   | Double of double
   | String of string
   | MyCustomType of MyCustomType

   with 
     member this.ConvertTo(newType: MyTypes) = 
       match this with 
       | ResultType.Boolean(value) -> 
           match newType with 
           | MyTypes.Boolean -> 
              this
           | MyTypes.Integer -> 
              ResultType.Integer(if value then 1 else 0)
          ...
       | ResultType.MyCustomType(value) -> 
           match newType with 
           | MyTypes.MyCustomType -> 
              this
           | MyTypes.String -> 
              ResultType.String(value.ToString()) 
           | _ -> 
              failwithf "Conversion from MyCustomType to %s is not supported" (newType.ToString())

I don't like such construction, because if I add more types, this requires me to do many changes: MyTypes, ResultType and also in several places in the ConvertTo member function.

Can anybody suggest better solution for such types conversion?

Thanks in advance


With a slightly different design, it is possible to exploit System.Convert.ChangeType and the fact that the constructors of discriminated unions are actually functions:

// statically typed wrapper for System.Convert.ChangeType
let conv a : 'T = System.Convert.ChangeType(a, typeof<'T>) :?> 'T

type MyCustomType() = class end

type ResultType = 
  | Boolean of bool
  | Integer of int
  | Decimal of decimal
  | Double of double
  | String of string
  | MyCustomType of MyCustomType
  with
    member this.ConvertTo (newType:'T->ResultType) =
      match this with
      | Boolean b -> newType( conv b )
      | Integer i -> newType( conv i )
      | Decimal d -> newType( conv d )
      | Double d -> newType( conv d )
      | String s -> newType( conv s )
      | MyCustomType m ->
         if typeof<'T> <> typeof<string> then
            raise (new System.InvalidCastException("MyCustomType can only be converted to String"))
         else
            String (m.ToString())

let i = Integer 42

let b = i.ConvertTo Boolean
printfn "%A" b

let d = i.ConvertTo Decimal
printfn "%A" d

let d2 = i.ConvertTo Double
printfn "%A" d2

let s = i.ConvertTo String
printfn "%A" s

//let mi = i.ConvertTo MyCustomType  // throws InvalidCastException

let m = MyCustomType (new MyCustomType())
let sm = m.ConvertTo String
printfn "%A" sm

//let im = m.ConvertTo Integer // throws InvalidCastException

EDIT: Once you add more custom types, this will not help much.

Maybe you should make your custom types implement IConvertible. Then you can remove the special case code from ConvertTo and completely rely on System.Convert.ChangeType.

You would still have to extend every custom type's ToObject implementation whenever you add a new custom type. Whether that really is better than a central ConvertTofunction is debatable.


Why are you wanting to do type conversion to begin with? Discriminated Unions are a good way of hiding type information until you need it and abstract complexity away. Generally you have a match statement in a function that consumes this type and then you only cast if you need to.

If you're trying to make some type of parser or language engine then you have no choice but to define all the cast or at least their error states. If you wouldn't mind elaborating on why / what you would use this for, maybe I could suggest another approach.

An aside: F# and .NET in general doesn't support overloading of return types.

0

精彩评论

暂无评论...
验证码 换一张
取 消