【问题标题】:F# structural tuples versus BCL Tuple typesF# 结构元组与 BCL 元组类型
【发布时间】:2014-03-19 03:59:00
【问题描述】:

在 F# 中,您可以如下定义 first 函数:

let first (x, y) = x

你可以这样称呼它:

first (1, 2)

您还可以根据 BCL Tuple 类型定义相同的函数:

let first (t:Tuple<_, _ >) = t.Item1

但是,你不能使用前面的语法调用它,否则你会得到以下错误:

error FS0001: The type ''c * 'd' is not compatible with the type 'Tuple<'a,'b>'

相反,您必须执行以下操作:

first (Tuple<_,_>(1, 2))

这很奇怪,因为编译后的 F# 代码似乎在任何一种情况下都使用Tuple 来表示其参数。那么为什么 F# 编译器告诉我类型不兼容呢?

为什么这很重要?好吧,基本上我想编写一个支持任意长度元组的重载方法。这对于 F# 的语法元组是不可能的,因为必须事先知道参数的确切数量。但是,使用 BCL Tuple 类型似乎是可能的,因为这些类型使用 TRest 技巧来允许任意长度的元组。不幸的是,如果我以这种方式编写重载,那么它们将无法与 F# 语法元组一起使用,这是最终目标。

所以我的问题是:为什么语法元组和 BCL 元组不兼容?此外,是否有任何编写函数和/或方法在 F# 中对任意长度元组进行操作的示例?

具体的应用程序处理我正在编写的基于类型推断的二进制解析库。您可以查看代码here。您可以看到我对元组的许多重载,但我不想将它们扩展到某个幻数。

【问题讨论】:

    标签: f# base-class-library


    【解决方案1】:

    我认为您对非常长的元组的观察部分回答了您的问题 - 在 F# 中,您可以拥有任意长度的元组,因此创建一个包含 9 个元素的元组是完全可以的:

    let t = (1,1,1,1,1,1,1,1,1)
    

    如果您使用 t.GetType() 查看运行时类型,那么它实际上会编译为嵌套的 .NET 元组 Tuple<int, int, int, int, int, int, int, Tuple<int, int>>

    我不确定这是否是明确的答案,但我认为它显示了问题的一部分——如果 F# 元组匹配 .NET 元组,那么它们要么必须限制为 8 个元素(以匹配 .NET元组类型),否则它们将是“泄漏”抽象,并且大型元组将(静默)匹配某些嵌套元组类型。

    如果您需要一个可以处理任意数量元素的函数,那么接受参数作为列表而不是元组可能更有意义?或者您可以编写一个使用 F# 反射(在Microsoft.FSharp.Reflection 中)处理任意大小的元组的函数...但我发现这对解析器很有用,而其他方法可能没有那么好。

    【讨论】:

    • 很遗憾他们没有 9,10,11,12,13,14,15,16。那本来是笛卡尔封闭的!
    • Tuple`1 类型用于区分长元组和嵌套元组。例如,(1,2,3,4,5,6,7,8,9) 映射到 Tuple<int, ..., Tuple<int, int>>,而 (1,2,3,4,5,6,7,(8,9)) 映射到 Tuple<int, ..., Tuple<Tuple<int, int>>。所以我不确定你提出的关于意外匹配的问题是否适用。
    • 不幸的是,在我的情况下,列表实际上并不适用,因为我的想法是能够处理一组不同的类型。主要是我只是试图避免提供大量的重载,以预期任何人可能想要对库做些什么,但这似乎是不可避免的。
    【解决方案2】:

    像往常一样 F# 规范来拯救:

    6.3.2 元组表达式

    expr1, ..., exprn 形式的表达式是一个元组表达式。例如:

    let three = (1,2,"3")
    let blastoff = (10,9,8,7,6,5,4,3,2,1,0)
    

    对于新类型 ty1 ... tyn,表达式具有类型 (ty1 * ... * tyn),并且使用初始类型 tyi 检查每个单独的表达式 ei。

    元组类型和表达式被转换为一系列名为 System.Tuple 的 F# 库类型的应用程序。元组类型 ty1 * ... * tyn 翻译如下:

    • 对于 n Tuple<ty1,...,tyn>。
    • 对于较大的 n,元组类型是附加 F# 库类型 System.Tuple&lt;_&gt; 的应用程序的简写,如下所示:
    • 对于 n = 8,详细形式为 Tuple&lt;ty1,...,ty7,Tuple&lt;ty8&gt;&gt;
    • 对于 9 Tuple<ty1,...,ty7,tyB>,其中 tyB 是类型 (ty8 ... tyn) 的转换形式。

    元组表达式(expr1,...,exprn)翻译如下:

    • 对于 n new Tuple<ty1,…,tyn>(expr1,...,exprn)。
    • 对于 n = 8,详细形式为 new Tuple&lt;ty1,…,ty7,Tuple&lt;ty8&gt;&gt;(expr1,...,expr7, new Tuple&lt;ty8&gt;(expr8)
    • 对于 9 new Tuple<ty1,...ty7,ty8n>(expr1,..., expr7, new ty8n(e8n) 其中 ty8n 是类型 (ty8*...* tyn) 而 expr8n 是表达式的详细形式 expr8,..., exprn.

    当被视为静态类型时,元组类型与其编码形式不同。但是,元组值和类型的编码形式在 F# 类型系统中通过运行时类型可见。例如,typeof 等价于 typeof&lt;System.Tuple&lt;int,int&gt;&gt;,并且 (1,2) 具有运行时类型 System.Tuple&lt;int,int&gt;。同样,(1,2,3,4,5,6,7,8,9) 的运行时类型为 Tuple&lt;int,int,int,int,int,int,int,Tuple&lt;int,int&gt;&gt;

    注意:在 .NET 4.0 F# 中将元组添加到 BCL 之前,使用 FSharp.Core dll 中定义的 System.Tuple 类型

    我想你处理具有任意大小的元组的唯一方法是使用Microsoft.FSharp.Reflection.FSharpType\FSharpValue中的函数进行构造和解构

    【讨论】:

    • 是的,FSharpValue 无法满足我的需求,因为它失去了静态类型。我想我只会提供一堆重载。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    相关资源
    最近更新 更多