【问题标题】:What are single and zero element tuples good for?单元素元组和零元素元组有什么用?
【发布时间】:2019-01-14 07:39:30
【问题描述】:

C# 7.0 引入了值元组以及对它们的一些语言级别的支持。他们added the support 也是单元素元组和零元素元组;但是,我找不到任何有用的场景。

通过ValueTuple.Create 重载,我可以创建任何类型的元组,但 C# 7.0 语法只允许至少两个元素:

Microsoft (R) Roslyn C# Compiler version 2.8.3.62923
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> ValueTuple.Create()
[()]
> ValueTuple.Create(1)
[(1)]
> ValueTuple.Create(1, "x")
[(1, x)]

按元组语法:

> var t2 = (1, 2);
> var t1 = (1); // t1 is now int (of course)
> ValueTuple<int> t1 = (1);
(1,23): error CS0029: Cannot implicitly convert type 'int' to 'ValueTuple<int>'
> ValueTuple<int> t1 = new ValueTuple<int>(1);
> t1
[(1)]

我想我找到了 this feature was requested 所在的线程,但现在没有任何代码示例在 C# 中有效,并且在 planned features of C# 8.0 中也找不到任何引用,甚至在递归元组模式中也找不到。

在请求线程中提到了函数式编程语言。是否有任何功能性语言现在使用它们?我不是 F# 专家,但它的元组 reference 没有提到任何使用单元素和零元素元组。

所以 TL;DR 问题:

  • 是否在任何(可能是功能性的).NET 语言中使用了单元素元组和零元素元组?我的意思不是Tuple.Create 或构造函数,而是本地语言支持。
  • 是否计划在未来的 C# 版本中使用它们?
  • 或者它们是为了“以防万一”,为了未来的兼容性?

【问题讨论】:

  • 我猜在 C# 中支持 (1) 是由于当前的解析规则而造成的重大变化。 (这就是它评估为 1 的原因。)

标签: c# f# tuples c#-7.0


【解决方案1】:

我想不出一个元素元组的用例。编辑 - 正如所指出的,引用的页面提到了一个元素元组作为命名返回参数的一种方式,这听起来实际上很有用,尽管这可能是 C# 特定的。

零元素元组在函数式语言中也称为unit。它相当于 C# 中的 void,一些(大多数?)函数式语言没有。

不同之处在于 () 是一个实际值,你可以用它来做一些事情,比如保存列表、模式匹配等。函数式语言需要这个,因为函数必须返回一些东西,如果你想返回“无”你必须通过返回 () 来显式执行此操作。您还可以编写一个接受单位作为参数的函数,这基本上意味着它是一个延迟执行的值,很可能有副作用:

let x = getFromDb() // will read DB
stream.Flush() // will change state

【讨论】:

  • 我想不出一个元素元组的用例。引用的 Github 页面提到了通过元组命名的返回变量。
  • 是的,单元在线程中被引用。即使在幕后可以将其编译为System.Void,它也可能很方便,但我只是好奇它是否真的在任何地方使用(或计划这样做)。
  • @taffer - unit 类型在 F# 中到处使用。我为现已失效的 Stack Overflow 文档写了一些关于它的内容,该文档太长而无法发表评论,所以我将把它搜罗起来并在这里变成另一个答案。 (编辑:完成,请参阅“0 元组有什么好处?”答案)。
【解决方案2】:

0 元组有什么好处?

2 元组或 3 元组表示一组相关项。 (2D 空间中的点、颜色的 RGB 值等)1 元组不是很有用,因为它可以很容易地用单个 int 替换。

0 元组似乎更无用,因为它完全不包含任何内容。然而,它具有使其在 F# 等函数式语言中非常有用的特性。例如,0 元组类型只有一个值,通常表示为()。所有 0 元组都有这个值,所以它本质上是一个单例类型。在包括 F# 在内的大多数函数式编程语言中,这称为 unit 类型。

在 C# 中返回 void 的函数将在 F# 中返回 unit 类型:

let printResult = printfn "Hello"

在 F# 交互式解释器中运行它,你会看到:

val printResult : unit = ()

这意味着值printResultunit 类型,并且具有值()(空元组,unit 类型的唯一值)。

函数也可以将unit 类型作为参数。在 F# 中,函数可能看起来没有参数。但事实上,他们采用了unit 类型的单个参数。这个函数:

let doMath() = 2 + 4

实际上等价于:

let doMath () = 2 + 4

也就是说,一个函数接受一个 unit 类型的参数并返回 int 值 6。如果您在定义此函数时查看 F# 交互式解释器打印的类型签名,您将看到:

val doMath : unit -> int

事实上,所有函数都会接受至少一个参数并返回一个值,即使该值有时是像() 这样的“无用”值,这意味着在 F# 中的函数组合比在不这样做的语言中容易得多没有unit 类型。但这是一个更高级的主题,我们稍后会讨论。现在,请记住,当您在函数签名中看到 unit 或在函数的参数中看到 () 时,这就是 0 元组类型,它表示“此函数接受或返回没有有意义的值。”

【讨论】:

  • P.S.我提到的“我们稍后会谈到的更高级的主题”是如何在 F# 中编写 all 函数。在 C# 中,您可以编写一个 Compose 方法,该方法接受 Func&lt;A, B&gt;Func&lt;B, C&gt; 并返回 Func&lt;A, C&gt;。但是Action&lt;T&gt; 会弄乱您的 Compose 方法,您必须编写四个变体:Compose(Func, Func)Compose(Func, Action)Compose(Action, Func)Compose(Action, Action)。不过,在 F# 中,您可以只编写一个 Compose 函数,它适用于所有函数:unit -&gt; unit 可以与 unit -&gt; int 组合,等等。
  • 感谢您的回答(赞成)。 F# unit 的底层 .NET 类型是 FSharp.Core.dll 中的 Microsoft.FSharp.Core.Unit。难道只是因为ValueTuple当时不存在吗?还是它们在语义上有所不同?
  • 我很确定这是因为当时不存在 ValueTuple。只要所有0元组都是相同类型,那么Microsoft.FSharp.Core.Unit可以被替换为0个元素的ValueTuple。但是,对于 F# 类型系统来说,() 的所有实例都是 same 类型非常重要。我还不知道 ValueTuple 的内部结构,所以我不知道它是否能做出这样的承诺。如果不能,那么这就是 F# 的 unit 类型和 ValueTuple 之间的语义差异:所有 F# 中 () 的实例相同类型
  • 要详细了解为什么组合如此重要(以及为什么currying 在 F# 中如此重要),您可以观看 Scott Wlaschin 最近的演讲“组合的力量”:youtube.com/watch?v=WhEkBCWpDas跨度>
  • 我从未遇到过任何文档说 () 是零元素元组,或者值是单元组。尽管如此,我一直或多或少地看到了其中的逻辑,所以你写的很有道理。
【解决方案3】:

在 C# 中不支持 o-tuple (nople) 和 1-tuple (oneple)。

然而,在基类库 (BCL) 上,为了保持连贯性并以防万一将来有人发现它的用途,有 ValueTupleValueTuple&lt;T&gt;。例如,一种具有 0 元组和 1 元组结构的语言。

ValueTuple 有一些附加价值。来自the documentation

ValueTuple 结构表示一个没有元素的元组。它主要用于它的静态方法,允许您创建和比较值元组类型的实例。它的辅助方法允许您实例化值元组,而无需显式指定每个值元组组件的类型。通过调用其静态 Create 方法,您可以创建具有零到八个组件的值元组。对于超过八个组件的值元组,您必须调用 ValueTuple 构造函数。

【讨论】:

    【解决方案4】:

    目前,ValueTuple'1 仅用于表示长元组,即使没有用于 1 元组的 C# 元组语法:ValueTuple&lt;T1, T2, T3, T4, T5, T6, T7, ValueTuple&lt;T8&gt;&gt; 是 8 元组的基础类型。

    至于 0-tuples 和 ValueTuple'0,我们推测它们可能会出现在未来的场景中(如果我没记错的话,可能是一些递归模式)。

    【讨论】:

      【解决方案5】:

      1 元组可以用作可空值的非空包装器。这在尝试添加到字典时很有用,例如:

      void Process<TKey>(TKey[] keys)
      {
          var dict = new Dictionary<TKey, Something>();
          var firstKey = keys[0];
          dict[firstKey] = new Something();
          ...
      }
      

      如果启用nullable(创建dict 时)将显示警告,因为TKey 可能是可空类型(API 可能愿意接受)并且Dictionary&lt;TKey, TValue&gt; 不接受null ;而:

      void Process<TKey>(TKey[] keys)
      {
          var dict = new Dictionary<ValueTuple<TKey>, Something>();
          var firstKey = ValueTuple.Create(keys[0]);
          dict[firstKey] = new Something();
          ...
      }
      

      不会显示警告。

      【讨论】:

        【解决方案6】:

        1-Tuple 可用作委托的单个参数列表。元组很适合用作委托参数列表,因为您可以更改它们的结构而不破坏委托的实现。您也可以通过这种方式命名委托的参数。但由于不能使用 1-Tuple,因此如果参数的数量变为 1,则必须恢复为常规参数。

        例如:

        int DoSomething(
           Func<(int First, int Second, int Third), int> function
        ) =>
           function((1, 2, 3));
        
        // notice that you could add more parameters or change the order
        // of the values and not break the lambda
        DoSomething(args => 
           args.First + args.Second - args.Third
        );
        
        // DOESN'T WORK
        int DoSomethingElse(
           Func<(int First), int> function
        ) =>
           function((1));
        
        // does work but not as nice
        int DoSomethingElse2(
           Func<int, int> function
        ) =>
           function(1);
        

        【讨论】:

          【解决方案7】:

          单值元组可用于描述方法的返回值。 有时提供有关返回值是什么的附加信息很有用,例如:

            (int ScreenNumber) AddingTarget(string stockItemCode); 
          

          【讨论】:

          • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 2013-01-16
          • 2016-12-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-12
          • 1970-01-01
          • 2012-03-16
          相关资源
          最近更新 更多