Scala - 柯里化和默认参数

Posted

技术标签:

【中文标题】Scala - 柯里化和默认参数【英文标题】:Scala - Currying and default arguments 【发布时间】:2011-08-06 06:52:23 【问题描述】:

我有一个带有两个参数列表的函数,我试图部分应用和使用柯里化。第二个参数列表包含所有具有默认值(但不是隐式)的参数。像这样的:

 def test(a: Int)(b: Int = 2, c: Int = 3)  println(a + ", " + b + ", " + c); 

现在,以下一切都很好:

 test(1)(2, 3);
 test(1)(2);
 test(1)(c=3);
 test(1)();

现在如果我定义:

 def partial = test(1) _;

那么可以做到以下几点:

 partial(2, 3);

有人可以解释为什么我不能在“部分”中省略一些/所有参数,如下所示:

 partial(2);
 partial(c=3);
 partial();

不应该写“部分”的行为本质上与“测试(1)”相同吗?有人可以帮我找出实现这一目标的方法吗?

请帮忙,我很绝望!

编辑 - 由于我无法在 24 小时内回答我自己的问题,我将在此处发布我自己的答案:

这是迄今为止我自己能做到的最好的:

class Test2(val a: Int) 
   def apply(b: Int = 2, c: Int = 3)  println(a + ", " + b + ", " + c); 


def test2(a: Int) = new Test2(a);
def partial2 = test2(1); // Note no underscore

test2(1)(2, 3);
test2(1)(2);
test2(1)(c=3);
test2(1)();

partial2(2, 3)
partial2(2);
partial2(c=3);
partial2();

这样就可以了……

【问题讨论】:

permalink.gmane.org/gmane.comp.lang.scala.user/36288 【参考方案1】:

类型推断引擎为partial 提供下一步的类型;即 eta 扩展 test(1) _。你可以看到例如在 REPL 中,partial 的类型为 (Int, Int) => Unit,而 test 的类型为 (a: Int)(b: Int,c: Int)Unit。 eta 扩展的结果是一个Function 对象,它不携带任何参数名称(因为可以使用匿名参数定义Function)。

要解决此问题,您必须按如下方式定义partial

def partial(b: Int = 2, c: Int = 3) = test(1)(b,c)

也许您需要考虑testpartial 都可以访问它们的默认值,以确保它们保持相等。但我不知道避免重复参数名称而不引入额外开销(如创建新对象等)的技巧。

【讨论】:

Function 对象确实带有参数名称,但这些是在 Function2 中定义的参数名称您实际上可以通过这种方式调用@Desperate 给出的示例:partial(v1 = 2, v2 = 3) 我认为这与编译器有关,不是为这样的函数创建新类,而是使用“通用”Function2。否则它本可以在注释中存储它需要的所有信息。不过,重点是避免重复参数名称/列表:( @Moritz,我意识到这一点,但是当人们想要保留专有名称时,这根本没有用。如果不是语言的缺陷,这确实是一种缺陷。我从来不明白为什么 Scala 试图重用预先创建的 Function 类而不是根据需要生成它们。我知道这会产生更多的类,但它既是正确的做法,最终也是有用的。在旁注中,我将尝试生成一个我自己的新的类似函数的对象,其中包含“应用”方法...... @Desperate 我并没有说它有用 - 如果您的参数碰巧以不同的顺序具有相同的名称,它甚至可能是有害的。 Scala 实际上确实会生成一个像这样的 FunctionN 案例的子类,但是在这里返回一个专门的子类型会使重载变得非常棘手。 这是迄今为止我自己能做的最好的:class Test2(val a: Int) def apply(b: Int = 2, c: Int = 3) println(a + ", " + b + ", " + c); def test2(a: Int) = new Test2(a); def partial2 = test2(1); // 注意没有下划线 test2(1)(2, 3);测试2(1)(2);测试2(1)(c=3);测试2(1)();部分2(2, 3) 部分2(2);部分2(c = 3);部分2();这样就可以了……【参考方案2】:

这是迄今为止我自己能做到的最好的:

class Test2(val a: Int) 
   def apply(b: Int = 2, c: Int = 3)  println(a + ", " + b + ", " + c); 


def test2(a: Int) = new Test2(a);
def partial2 = test2(1); // Note no underscore

test2(1)(2, 3);
test2(1)(2);
test2(1)(c=3);
test2(1)();

partial2(2, 3)
partial2(2);
partial2(c=3);
partial2();

这样就可以了……

【讨论】:

【参考方案3】:

根据您的评论,这里有一个更简洁的写法:

def test(a: Int) = new 
  def apply(b: Int = 2, c: Int = 3) 
    println(a + ", " + b + ", " + c)
  

这比您的建议更紧凑,但效率较低,因为对内部 apply 的任何调用都将通过反射发生,就像结构类型一样。其实test的返回类型是结构类型:

 java.lang.Objectdef apply(b: Int,c: Int): Unit; def apply$default$1: 
 Int @scala.annotation.unchecked.uncheckedVariance; def apply$default$2: Int 
 @scala.annotation.unchecked.uncheckedVariance

【讨论】:

以上是关于Scala - 柯里化和默认参数的主要内容,如果未能解决你的问题,请参考以下文章

java8入门必备—函数式编程思维——函数式语言向语言和运行时让渡控制权的途径——柯里化和函数的部分施用

js高阶函数应用—函数柯里化和反柯里化

使用 lodash 在打字稿中使用默认参数进行柯里化

Scala柯里化及其应用实践

Scala中的柯里化

Scala 柯里化