如何在Scala中将tuple应用于格式化字符串?

3

编辑1

我已经看到了这个问题:在Scala中将函数应用于元组

理想情况下,我希望可以像这样简单地执行:

scala> val t = ("A", "B", "C")
t: (java.lang.String, java.lang.String, java.lang.String) = (A,B,C)

scala> "%-10s %-50s %s".format(t) // or some closer syntax

这应该会输出以下内容

res12: String = A          B                                                  C

编辑2

或者在某种意义上,Scala编译器应该能够推断出我实际上是使用正确的参数和类型进行调用,使得

"%-10s %-50s %s".format(t.untuple) 扩展为 "%-10s %-50s %s".format(t._1, t._2, t._3)

我可以使用宏来实现这个吗?

原始问题如下

我有一个元组,用于格式化字符串:

scala> val t = ("A", "B", "C")
t: (java.lang.String, java.lang.String, java.lang.String) = (A,B,C)

scala> "%-10s %-50s %s".format(t.productElements.toList: _*)
warning: there were 1 deprecation warnings; re-run with -deprecation for details
res10: String = A          B                                                  C

scala> "%-10s %-50s %s".format(t._1, t._2, t._3)
res11: String = A          B                                                  C

到目前为止,一切都很好。但是下面的内容失败了:

scala> val f = "%-10s %-50s %s".format(_)
f: Any* => String = <function1>

scala> f(t.productElements.toList: _*)
warning: there were 1 deprecation warnings; re-run with -deprecation for details
java.util.MissingFormatArgumentException: Format specifier '-50s'
    at java.util.Formatter.format(Formatter.java:2487)
    at java.util.Formatter.format(Formatter.java:2423)
    at java.lang.String.format(String.java:2797)
    at scala.collection.immutable.StringLike$class.format(StringLike.scala:270)
    at scala.collection.immutable.StringOps.format(StringOps.scala:31)
    at $anonfun$1.apply(<console>:7)
    at $anonfun$1.apply(<console>:7)
    at .<init>(<console>:10)
    at .<clinit>(<console>)
    at .<init>(<console>:11)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
    at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
    at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
    at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
    at java.lang.Thread.run(Thread.java:744)

这也失败了:
scala> f.apply(t)
java.util.MissingFormatArgumentException: Format specifier '-50s'
    at java.util.Formatter.format(Formatter.java:2487)
    at java.util.Formatter.format(Formatter.java:2423)
    at java.lang.String.format(String.java:2797)
    at scala.collection.immutable.StringLike$class.format(StringLike.scala:270)
    at scala.collection.immutable.StringOps.format(StringOps.scala:31)
    at $anonfun$1.apply(<console>:7)
    at $anonfun$1.apply(<console>:7)
    at .<init>(<console>:10)
    at .<clinit>(<console>)
    at .<init>(<console>:11)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
    at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
    at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
    at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
    at java.lang.Thread.run(Thread.java:744)

我做错了什么?如何将元组参数应用于“varagrs”样式的函数?

1
我可以问一下为什么您需要使用元组进行格式化吗?为什么不使用其他方法呢?例如,字符串插值是一种从某些值创建插值字符串的简洁方式。 - Nader Ghanbari
我知道我可以使用字符串插值。但我只想使用字符串格式化。使用字符串插值语法,我必须为元组使用下划线,这有点丑。 - tuxdna
2个回答

5
问题出在lambda定义中。
`val f = "%-10s %-50s %s".format(_)`
等同于:
`val f = x => ("%-10s %-50s %s".format)(x)`
所以你在这里只是将序列作为第一个参数传递。
正确的lambda表达式是:`(x: Seq[Any]) => ("%-10s %-50s %s".format)(x: _*)` ,甚至可以简写成 `val f: Seq[Any] => String = "%-10s %-50s %s".format`
例子:
scala> val f = x => ("%-10s %-50s %s".format)(x)
f: Seq[Any] => String = <function1>

scala> f(Seq(1,2,3))
java.util.MissingFormatArgumentException: Format specifier '-50s'

scala> val f: Seq[Any] => String = "%-10s %-50s %s".format
f: Seq[Any] => String = <function1>

scala> f(Seq(1,2,3))
res75: String = 1          2                                                  3

不幸的是,Scala不理解(“%-10s%-50s%s” .format)(_: _ *),因此您无法使用下划线。

这里有趣的是:如果您尝试使用下划线而没有应用程序完成相同的操作-您不必明确指定_ *:

scala> val f = ("%-10s %-50s %s": scala.collection.immutable.StringLike[_]).format _
f: Seq[Any] => String = <function1>

scala> f(Seq(1,2,3))
res81: String = 1          2                                                  3

但是您必须明确指定类型类,因为 function _ 构造不能使用隐式参数


然而,这仍然不是我希望的类型安全方式。元组的所有成员都会转换为“Any”。 - tuxdna
format函数本身被定义为Any*,所以你必须接受它 :) - dk14

2
您可以如下定义f
def f(t: Product) = "%-10s %-50s %s".format(t.productIterator.toList: _*)

然后您可以将其应用于t:
scala> f(t)
res1: String = A          B                                                  C

什么是Product

scala.Product是一个由许多类实现的trait,例如Product2Product3。这些类又分别是Tuple2Tuple3的超类型。

因此,任何元组都是一个Product。这就是为什么所有元组都可以使用productIterator方法的原因。

所以,为了将元组用作可变参数的一种方法是使用productIterator,然后调用toList方法将其转换为List

更好的签名

也许下面的签名是您函数的更好签名:

 def f(fmt: String)(t:Product) = fmt(t.productIterator.toList: _*)

现在,您可以应用(或部分应用)它以获得所需的结果:
val res = f("%-10s %-50s %s")(t)
val newFormatter = f("%-10s %-40s %s")_
val newResult = newFormatter(t)

请注意,您可以将f函数放在任意类中并将其重命名为format,然后提供从字符串到该类的隐式转换。然后,通过导入该隐式转换并启用隐式转换,您可以在字符串上调用格式化方法并像这样传递元组:"%s".format(tuple) - Nader Ghanbari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接