Scala:使用参数和可调用来定义函数

时间:2018-02-06 11:34:15

标签: scala

如果我想定义一个带有一个或多个参数和一个可调用函数(一个函数)的函数,以及为什么注释是这样的,我想知道它是如何工作的。

我将以此answer为例:

// Returning T, throwing the exception on failure
  @annotation.tailrec
  final def retry[T](n: Int)(fn: => T): T = {
    util.Try { fn } match {
      case util.Success(x) => x
      case _ if n > 1 => retry(n - 1)(fn)
      case util.Failure(e) => throw e
    }
  }

在这个函数中有一些有趣的东西:

  1. 注释tailrec。
  2. 通用类型函数retry[T]
  3. 参数int
  4. callable fn
  5. 我的问题在第4点。为什么以及如何使用two圆括号来定义此函数。如果要将可调用函数传递给任何函数,是否应始终使用可选参数“list”旁边的圆括号?为什么不把参数放在一起?

    提前谢谢

3 个答案:

答案 0 :(得分:1)

说实话,您不必使用多个参数列表来传递函数作为参数。 E.g。

document.body.appendChild(link);
link.click();

会完全奏效。但是,你怎么称呼它?

def pass(string: String, fn: String => Unit): Unit = fn(string)
有些人会发现它很笨拙。你宁愿想传递它:

pass("test", s => println(s))

甚至

pass("test")(s => println(s))

使其看起来好像函数是附加到pass("test") { s => println(s) } 的一个块,该块用一个参数调用。

使用单个参数列表,您将被强制写入:

pass

使用最后一个参数curry,您只需获得更舒适的语法。在像Haskell这样的语言中,curry会自动发生,你不必为像这样的句法设计决定而烦恼。不幸的是,Scala要求您明确做出这些决定。

答案 1 :(得分:1)

您可以在函数声明中包含多个参数列表。它与将所有参数合并到一个列表(def foo(a: Int)(b: Int)或多或少等同于def foo(a: Int, b: Int))大致相同,但有一些区别:

  • 您可以在声明中引用先前列表中的参数:def foo(a : Int, b: Int = a + 1)不起作用,但def foo(a: Int)(b: Int = a +1)不起作用。

  • 可以在参数列表之间推断类型参数:def foo[T](x: T, f: T => String): String ; foo(1, _.toString)不起作用(您必须编写foo[Int](1, _.toString),但def foo[T](x: T)(f: T => String); foo(1)(_.toString)会这样做。< / p>

  • 您只能将整个列表声明为隐式,因此,当您需要隐式某些参数而不是其他参数时,多个列表会很有用:def foo(a: Int)(implicit b: Configuration)

然后,有一些语法上的优势 - 你可以用单个列表做的事情,但他们只是看起来更丑陋:

  • 柯里:

    def foo(a: Int)(b: Int): String
    val bar: Int => String = foo(1)
    

    你也可以用单个列表写这个,但它看起来不太好看:

     def foo(a: Int, b: Int): String
     val bar: Int => String = foo(1, _)
    

最后,问你的问题:

def retry[T](n: Int)(f: => T) 

很好,因为只有一个参数的列表周围的括号是可选的。所以,这可以让你写出像

这样的东西
retry(3) { 
  val c = createConnection
  doStuff(c)
  closeConnection(c)
}
如果将f合并到同一个列表中,那么看起来会更加丑陋。 另外,currying很有用:

val transformer = retry[Int](3)

Seq(1,2,3).map { n => transformer(n + 1) }
Seq(4,5,6).map { n => transformer(n * 2) }

答案 2 :(得分:0)

  

如果你想将可调用函数传递给任何函数,你应该这样做   总是在&#34;列表旁边使用圆括号&#34;可选参数?   为什么不把参数放在一起?

没有这样的义务,它(大部分)是风格的问题。 IMO,它也导致更清晰的语法。使用两个参数列表,其中第二个是产生结果的函数,我们可以调用retry方法:

val res: Try[Int] retry(3) {
  42
}

相反,我们使用单个参数列表,我们的方法调用如下所示:

val res: Try[Int] = retry(3, () => {
  42
})

我找到第一个语法清理器,它还允许您在仅提供第一个参数列表时使用retry作为curried方法

但是,如果我们考虑更高级的用例,Scala类型推断在参数列表之间,而不是在它们内部。这意味着如果我们有一个方法:

def mapFun[T, U](xs: List[T])(f: T => U): List[U] = ???

我们可以调用我们的方法,而无需在呼叫站点指定TU的类型:

val res: List[Int] = mapFun(List.empty[Int])(i => i + 1)

但是如果我们使用单个参数列表,

def mapFun2[T, U](xs: List[T], f: T => U): List[U] = ???

这不会编译:

val res2 = mapFun2(List.empty[Int], i => i + 1)

相反,我们需要写:

val res2 = mapFun2[Int, Int](List.empty[Int], i => i + 1)

帮助编译器选择正确的类型。