Преобразование между типами в дискриминируемых союзах

У меня есть функция, которая может возвращать разные типы, и для этого я использую размеченное объединение. Что мне нужно, так это преобразование одного типа в размеченное объединение в другой тип. Также некоторые типы могут быть преобразованы во все другие типы (String), но некоторые типы могут быть преобразованы только в String (MyCustomType).

Для этого я добавил метод члена ConvertTo в 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())

Мне не нравится такая конструкция, потому что если я добавлю больше типов, это потребует от меня много изменений: MyTypes, ResultType, а также в нескольких местах в ConvertTo функция-член.

Кто-нибудь может предложить лучшее решение для преобразования таких типов?

заранее спасибо


person Vitaliy    schedule 15.03.2011    source источник
comment
Если вы добавите больше типов, матрица преобразований будет O (N ^ 2), поэтому, конечно, есть много изменений, по сути, это большая проблема.   -  person Brian    schedule 15.03.2011
comment
@Брайан. Я не уверен, что это можно использовать здесь и как правильно реализовать, но можно ли использовать какой-то промежуточный тип MyIntermediateClass для преобразования и преобразовать в другие типы, например: Double - ›MyIntermediateClass -› MyCustomClass - в данном случае это будет O (2 * N).   -  person Vitaliy    schedule 16.03.2011


Ответы (2)


При немного другом дизайне можно использовать System.Convert.ChangeType и тот факт, что конструкторы размеченных объединений на самом деле являются функциями:

// 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

РЕДАКТИРОВАТЬ: после того, как вы добавите больше настраиваемых типов, это не сильно поможет.

Может быть, вам стоит сделать так, чтобы ваши пользовательские типы реализовали IConvertible. Затем вы можете удалить код особого случая из ConvertTo и полностью полагаться на System.Convert.ChangeType.

Вам все равно придется расширять ToObject реализацию каждого настраиваемого типа всякий раз, когда вы добавляете новый настраиваемый тип. Спорный вопрос, действительно ли это лучше, чем центральная ConvertTo функция.

person wmeyer    schedule 15.03.2011
comment
Спасибо, это самый простой и понятный код, чем у меня. Это также исправляет мою ошибку, когда я добавляю еще один тип перечисления. Переопределение интерфейса IConvertible поможет с преобразованием в типы CLR (но я использую только некоторые из них - другие будут избыточными). Также откройте вопрос об использовании одной большой функции ConvertTo или добавьте несколько реализаций ToObject в пользовательские типы. - person Vitaliy; 16.03.2011

Почему вы хотите для начала выполнить преобразование типов? Дискриминационные союзы - хороший способ скрыть информацию о типе до тех пор, пока она вам не понадобится, и отвлечься от сложности. Обычно у вас есть оператор соответствия в функции, которая использует этот тип, а затем вы выполняете приведение только в случае необходимости.

Если вы пытаетесь создать какой-либо тип парсера или языкового движка, у вас нет другого выбора, кроме как определить все приведенные типы или, по крайней мере, их состояния ошибок. Если вы не против уточнить, почему и для чего вы бы это использовали, возможно, я мог бы предложить другой подход.

В стороне: F # и .NET в целом не поддерживают перегрузку возвращаемых типов.

person gradbot    schedule 15.03.2011
comment
Спасибо за ответ. Да, это используется парсером, который имеет сопоставимое большое синтаксическое дерево. Результат каждой функции зависит от результата (и типа результата) дочерних элементов (тип возвращаемого значения может быть различным для разных дочерних элементов - в зависимости от внутренней операции). Некоторые элементы в AST требуют преобразования в другой тип. Я думал о какой-то унифицированной функции, но она будет очень большой, если я, например, использую 30 пользовательских классов. Вот почему я спросил о предложении - возможно, кто-то знает о каком-то особом алгоритме, о каком-то другом подходе для этого - person Vitaliy; 15.03.2011
comment
Возможным решением будет расширение типов в размеченное объединение и добавление личного метода ConvertTo к каждому типу. Например: изменить | Логическое значение от bool до | Boolean of BooleanType и добавьте ConvertTo к новому типу BooleanType - person Vitaliy; 15.03.2011
comment
Я не уверен, как это поможет. Если вам действительно нужно this.ConvertTo, тогда вам придется грызть пулю. Я бы использовал подфункции и оператор подстановочного знака _, чтобы повысить удобочитаемость. Попытка избавиться от this.ConvertTo может просто принудить всю эту логику к остальной части вашего кода и, скорее всего, приведет к дублированию кода. - person gradbot; 15.03.2011