2012-02-16 4 views
2

Как я могу совместить случаи соединения динамически в F #, когда есть объявления о значениях?Проверка корпусов корпусов динамически

Non рабочего код:

let myShape = Shape.Square 
expect myShape Shape.Circle 

type Shape = 
    | Circle of int 
    | Square of int 
    | Rectangle of (int * int) 

let expect someShape someUnionCase = 
    if not (someShape = someUnionCase) 
    then failwith (sprintf "Expected shape %A. Found shape %A" someShape someUnionCase) 

let myShape = Shape.Square 
expect myShape Shape.Circle // Here I want to compare the value types, not the values 

Если мои дела профсоюзов не объявляло значение, это работает с использованием образцов Инстанцирования (что не то, что я хочу):

let myShape = Shape.Square 
expect myShape Shape.Circle 

type Shape = 
    | Circle 
    | Square 
    | Rectangle 

let expect someShape someUnionCase = 
    if not (someShape = someUnionCase) 
    then failwith (sprintf "Expected shape %A. Found shape %A" someShape someUnionCase) 

let myShape = Shape.Square 
expect myShape Shape.Circle // Comparing values instead of types 

ответ

3

При вызове expect функции в вашем примере, например Shape.Square как аргумент, вы фактически передаете ему функцию, которая принимает аргументы в случае объединения и создает значение.

Анализ динамических функций довольно сложно, но вместо этого вы можете передать конкретные значения (например, Shape.Square(0)) и проверить, что их форма одинакова (игнорируйте числовые аргументы). Это можно сделать, используя отражение F #. FSharpValue.GetUnionFields функция возвращает имя случае объекта, вместе с obj[] всех аргументов (которые можно проигнорировать):

open Microsoft.FSharp.Reflection 

let expect (someShape:'T) (someUnionCase:'T) = 
    if not (FSharpType.IsUnion(typeof<'T>)) then 
    failwith "Not a union!" 
    else 
    let info1, _ = FSharpValue.GetUnionFields(someShape, typeof<'T>) 
    let info2, _ = FSharpValue.GetUnionFields(someUnionCase, typeof<'T>) 
    if not (info1.Name = info2.Name) then 
     failwithf "Expected shape %A. Found shape %A" info1.Name info2.Name 

Если теперь сравнить Square с Circle, функция броска, но если сравнивать два Squares, он работает (даже если значения отличаются):

let myShape = Shape.Square(10) 
expect myShape (Shape.Circle(0)) // Throws 
expect myShape (Shape.Square(0)) // Fine 

Если вы хотите, чтобы избежать создания конкретных значений, вы можете также использовать F # цитаты и написать что-то вроде expect <@ Shape.Square @> myValue. Это немного сложнее, но, возможно, лучше. Некоторые примеры обработки котировок can be found here.

+0

Вау, это было уродливо. Я ожидаю ужасного удара производительности. Я использую его для проверки анализируемого дерева AST для ожидаемой используемой организации кода. –

+0

Учитывая, что Джон Палмерс понимает, что все, что я хочу сделать, - это сравнить два целых числа, которые у меня есть, используя отражение или время генерации кода IL, кажется абсурдным. –

+0

Все это относится к типам, которые не представлены значениями (объектами) в среде CLR , Это связано с тем, что Anders Heijsbergs Cool (позже C#) был смоделирован после Java. Который восходит к Гослингу, используя конструкторы вместо методов класса, не имея того факта, что Bjarne Strousup сделал бесцельный кошмар, потому что объект типа просто не соответствовал оригинальному инструменту перевода C++ в C.Такие вещи, как «конструкторы» и «статические» вместо функций-членов типа, были COMPROMISE из-за cfront. Позор Гослинга и Андерса за то, что они не понимают мотивацию беспринципного типа. –

4

Интересно, что это можно сделать очень легко в C#, но компилятор F # не позволит вам вызывать функции - что кажется странным.

В спецификации говорит о том, что дискриминация союз будет иметь (раздел 8.5.3):

Один экземпляр CLI свойство u.Tag для каждого случая C, который извлекает или вычисляет целое тег, соответствующий случаю.

Таким образом, мы можем написать функцию ожидать в C# тривиальным

public bool expect (Shape expected, Shape actual) 
{ 
    expected.Tag == actual.Tag; 
} 

Это интересный вопрос, почему это не может быть сделано в F # код, спецификация не появляется, чтобы дать хорошая причина.

+0

Действительно полезная информация :-) Как досадно тогда прибегать к генерации или отражению кода времени исполнения –

+0

Я рад, что реализация F # реализует типы таким образом. Это мелочь, что мы придерживаемся оригинальной философии типа C#/VB на языках .Net, хотя –

+0

@JoachimWester: это не философия типа C#/VB, а как была разработана CLS. Язык на основе .NET, очевидно, должен соответствовать CLS на определенном уровне. – ShdNx

1

Я использую тот же шаблон для реализации проверки типов в HLVM. Например, при индексировании в массив я проверяю, что тип выражения представляет собой массив, игнорирующий тип элемента. Но я не использую размышления, как предложили другие ответы.Я просто сделать что-то вроде этого:

let eqCase = function 
    | Circle _, Circle _ 
    | Square _, Square _ 
    | Rectangle _, Rectangle _ -> true 
    | _ -> false 

Обычно в более конкретной форме, как это:

let isCircle = function 
    | Circle _ -> true 
    | _ -> false 

Вы также можете сделать:

let (|ACircle|ASquare|ARectangle|) = function 
    | Circle _ -> ACircle 
    | Square _ -> ASquare 
    | Rectangle _ -> ARectangle 

Если вы решите пойти отражение маршрут и производительность - проблема (отражение невероятно медленно), то используйте предварительно вычисленные формы:

let tagOfShape = 
    Reflection.FSharpValue.PreComputeUnionTagReader typeof<Shape> 

Это более 60 × быстрее прямого отражения.

0

ПРИМЕЧАНИЕ это имеет оговорку. См. ОБНОВЛЕНИЕ ниже.

Похоже, что случаи объединения реализованы как вложенные классы типа объединения (имя типа: FSI_0006+Shape+Square). Поэтому для экземпляра типа объединения достаточно проверить тип экземпляра на obj.GetType().

let expect (someShape:'T) (someUnionCase:'T) = 
    if (someShape.GetType() <> someUnionCase.GetType()) then failwith "type not compatible" 

type Shape = 
    | Circle of int 
    | Square of int 
    | Rectangle of (int * int) 

let myShape = Shape.Square 12 
printfn "myShape.GetType(): %A" (myShape.GetType()) 
expect myShape (Shape.Circle 5) 

Это выходы:

myShape.GetType(): FSI_0006+Shape+Square 
System.Exception: type not compatible 
    at Microsoft.FSharp.Core.Operators.FailWith[T](String message) 
> at FSI_0006.expect[T](T someShape, T someUnionCase) 
    at <StartupCode$FSI_0006>[email protected]() 
Stopped due to error 

Я просто не знаю, если этот подход рассматривается в зависимости от реализации, то есть, некоторые платформы/выполнения реализует это по-разному, так что типы двух различных объектов союза дела одинаковы.

UPDATE

OK Я нашел выше не работает для типа накидной со случаями, которые не принимают параметров. В этом случае реализация случаев отличается, и .GetType() всегда дает тип объявления типа объединения. Ниже код демонстрирует это:

type Foo = A|B|C 
type Bar = X|Y|Z of int 

let getType (x:obj) = x.GetType() 
let p (x:obj) = printfn "%A" x 

A |> getType |> p 
B |> getType |> p 
C |> getType |> p 
X |> getType |> p 
Y |> getType |> p 
Z 7 |> getType |> p 

Это дает:

FSI_0004+Foo 
FSI_0004+Foo 
FSI_0004+Foo 
FSI_0004+Bar+_X 
FSI_0004+Bar+_Y 
FSI_0004+Bar+Z 

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

open Microsoft.FSharp.Reflection 

// more general solution but slower due to reflection 
let obj2Tag<'t> (x:obj) = 
    FSharpValue.GetUnionFields(x, typeof<'t>) |> fst |> (fun (i: UnionCaseInfo) -> i.Tag) 

[A;B;C;A] |> List.map obj2Tag<Foo> |> p 
[X;Y;Z 2; Z 3; X] |> List.map obj2Tag<Bar> |> p 

Это дает:

[0; 1; 2; 0] 
[0; 1; 2; 2; 0] 

Это должно быть значительно медленнее при работе на большом количестве объектов, так как оно сильно зависит от отражения.

Смежные вопросы