F# 动态检查联合案例

F# 动态检查联合案例,f#,F#,当存在值声明时,如何在F#中动态地匹配联合案例 非工作代码: 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 )

当存在值声明时,如何在F#中动态地匹配联合案例

非工作代码:

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
如果我的联合案例没有声明值,那么这将使用实例化示例(这不是我想要的):


当您使用示例中的
expect
函数(例如
Shape.Square
作为参数)调用该函数时,实际上是在向其传递一个函数,该函数接受并集大小写的参数并生成一个值

动态分析函数非常困难,但是您可以将具体的值传递给它(如
Shape.Square(0)
),并检查它们的形状是否相同(忽略数字参数)。这可以通过F#反射来实现。
FSharpValue.GetUnionFields
函数返回对象大小写的名称,以及所有参数的
obj[]
(您可以忽略这些参数):


如果您想避免创建具体的值,还可以使用F#引号并编写类似于
expect myValue
的内容。这有点复杂,但可能更好。引用处理的一些示例。

有趣的是,这在C#中可以很容易地完成,但是F#编译器不允许您调用函数-这似乎很奇怪

规范规定,有歧视的工会有(第8.5.3节):

一个CLI实例属性u.标记,用于获取或 计算与大小写对应的整数标记

所以我们可以用C#编写您的expect函数

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

这是一个有趣的问题,为什么不能在F#代码中完成,规范似乎没有给出很好的理由。

我使用相同的模式在HLVM中实现类型检查。例如,当索引到数组中时,我检查表达式的类型是否为数组,忽略元素类型。但我没有像其他答案所建议的那样使用反思。我只是这样做:

let eqCase = function
  | Circle _, Circle _
  | Square _, Square _
  | Rectangle _, Rectangle _ -> true
  | _ -> false
let isCircle = function
  | Circle _ -> true
  | _ -> false
通常以如下更具体的形式:

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>
let tagOfShape=
Reflection.FSharpValue.PreComputer标记读取器类型

这比直接反射快60倍以上。

注意这有一个警告。请参阅下面的更新

联合用例似乎是作为联合类型的嵌套类实现的(类型名称:
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)
这使得:

FSI_0004+Foo
FSI_0004+Foo
FSI_0004+Foo
FSI_0004+Bar+_X
FSI_0004+Bar+_Y
FSI_0004+Bar+Z
[0; 1; 2; 0]
[0; 1; 2; 2; 0]
另一个答案中提到的更通用的替代方案是将案例实例转换为标记:

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

如果在大量对象上操作,这应该会慢得多,因为它严重依赖于反射。

非常有用的信息:-)那么不得不求助于运行时代码生成或反射是多么烦人啊。我很高兴F#实现确实以这种方式实现了类型。我们坚持.Net语言中最初的C#/VB类型哲学,这是一件小事though@JoachimWester:这不是C#/VB类型的理念,而是CLS是如何设计的。基于.NET的语言显然必须在某种程度上与CLS相对应。@ShdNx Anders Cool project(成为C#)决定采用Java模型。但是,CLR没有问题。编译器可以为每个类创建一个singleton对象,并将所有静态方法作为成员函数应用于该singleton(包括new)。问题不在于CLR,而在于.NET framework类和C#编译器没有使用此单例类型对象。所以我不同意,这是原创哲学,而不是CLR。“CLR是它应该是IMHO的样子。@JoachimWester:你在哪里看到我提到CLR的?我同意,CLR不能不关心这些事情。然而,CLS(公共语言规范)就是这样设计的。哇,这太难看了。我期待一场糟糕的演出。我用它来验证解析后的AST树是否符合预期使用的代码组织。鉴于John Palmers的见解,我想做的就是比较手头上的两个整数,使用反射或运行时IL代码生成似乎很荒谬。这一切都可以追溯到CLR中没有值(对象)表示的类型。这可以追溯到Anders Heijsbergs Cool(后来被称为C#)是模仿Java建模的事实。追溯到GORLIN使用构造函数而不是类方法,错过了Bjarne Strousup做无类型噩梦的事实,因为一个类型对象不适合原始的cFordC++到C翻译工具。像“构造函数”和“静态”之类的东西代替类型成员函数是一种折衷,因为cfront。Gosling和Anders不理解无类型类型的动机,真让人感到羞耻。@JoachimWester我想你可以采取完全不同的观点,这取决于你是从OO(即SmallTalk)的角度还是从functionel(即ML)的角度看问题。我相信对于函数式编程,无法将类型视为值与在OO语言中一样错误,尽管OO中的顺序更为严重。
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]