Как неявно преобразовать в общие супертипы в совпадениях с образцом F #? - PullRequest
7 голосов
/ 11 октября 2010

Краткое описание проблемы

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

Пример

Предположим, у меня есть некоторая иерархия классов:

type Foo () =
    abstract member Value : unit -> string

type A (i:int) = 
    inherit Foo ()
        override this.Value () = i.ToString()

type B (s:string) = 
    inherit Foo ()
        override this.Value () = s

В идеале и в некоторыхна языках программирования в обычном режиме я бы написал эквивалент следующего:

let bar (i:int) : Foo =
    match i with
      | 1 -> B "one"
      | _ -> A i

Однако это не дает возможности правильно проверить тип, выдавая ошибку: «Ожидалось, что это выражение будет иметь тип Foo, но здесь имеет тип B».Я не понимаю, почему у компилятора недостаточно информации, чтобы вывести общий супертип для выражения соответствия, а затем проверить, что общим супертипом является 'Foo'.

В настоящее время я вынужден предоставитьявное принуждение для каждого случая в сопоставлении с образцом:

let bar2 (i:int) : Foo =
    match i with
      | 1 -> (B "one") :> Foo
      | _ -> (A i) :> Foo

Я бы хотел избежать этого.

Дополнительные примечания

  • Интуиция предполагает, что это является результатом более общей проблемы.Хотя я бы подумал, что что-то такое же общее, как сопоставление с образцом, или если операторы, которые также демонстрируют то же свойство, будут иметь правило проверки типа для учета общих супертипов.
  • Прежде чем кто-либо предложит - я ценю, что еслиA или B были бы объектными выражениями, это работало бы, но мой реальный пример - создание экземпляров классов C #, где они являются обычными классами.
  • Есть ли способ для меня объявить функции для неявного преобразования типов, как, например, scalaимеет, так что я могу применить автоматические преобразования для модуля, где я делаю это поколение?

Спасибо за любую помощь по этому вопросу.

Ответы [ 2 ]

7 голосов
/ 11 октября 2010

Я бы использовал upcast, а-ля

[<AbstractClass>]
type Foo () = 
    abstract member Value : unit -> string 

type A (i:int) =  
    inherit Foo () 
    override this.Value () = i.ToString() 

type B (s) =  
    inherit Foo () 
    override this.Value () = s 

let bar2 i : Foo = 
    match i with 
    | 1 -> upcast B "one"
    | _ -> upcast A i

Вам все еще нужно добавить его в каждую ветвь, но это часто предпочтительнее приведения к типу, поскольку часто имя типа имеет длину 20 или 30 символов (MyNamespace.ThisThingy), тогда как upcast - это всего 6 символов.

Но, вкратце, языковые правила не допускают ничего другого, типы всех ветвей должны быть одинаковыми.

5 голосов
/ 12 октября 2010

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

Вы можете использовать очень простое вычислительное выражение , которое имеет только Return член. Строитель будет иметь параметр типа, а Return будет ожидать значения этого типа. Хитрость в том, что F # делает вставку автоматических обновлений при вызове участника. Вот декларация:

type ExprBuilder<'T>() =
  member x.Return(v:'T) = v

let expr<'T> = ExprBuilder<'T>()

Чтобы написать простое сопоставление с шаблоном, которое возвращает что-либо как obj, теперь вы можете написать:

let foo a = expr<obj> {
  match a with
  | 0 -> return System.Random()
  | _ -> return "Hello" }

Вы все еще должны четко указывать тип возвращаемого значения (при создании выражения для вычисления), но я нахожу синтаксис довольно аккуратным (но это определенно хитрое использование, которое может запутать людей, которые увидят его впервые) .

...