2

I was wondering how it works if I want to defining a function that takes one or more parameters and a callable (a function), and why the annotation is like this.

I will take the code from this answer as example:

// 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
    }
  }

In this function there are a few interesting things:

  1. The annotation tailrec.
  2. Generic type function retry[T]
  3. Parameter int
  4. callable fn

My question is on point 4. Why and how the definition of this function takes two round brackets. If you want to pass a callable function to any function should you always use a round brackets next to the "list" of optional parameter? Why not put together with the parameters?

Thank you in advance

salvob
  • 1,300
  • 3
  • 21
  • 41

3 Answers3

1

To be honest you don't have to use multiple parameter lists in order to pass function as an argument. E.g.

def pass(string: String, fn: String => Unit): Unit = fn(string)

would totally work. However, how would you call it?

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

Some would find it clumsy. You would rather want to pass it like:

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

or even

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

to make it look as if function is a block appended to the pass called with one parameter.

With single parameter list you will be forced to write it like:

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

With last parameter curried you just get more comfortable syntax. In languages like Haskell, where currying happens automatically, you don't have to bother with syntactic design decisions like this one. Unfortunately Scala requires that you made these decisions explicitly.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
  • Thank Mateusz, very clear. One additional question... Do you think is possible in my example code, getting the name of the function `fn` that will be called? – salvob Feb 06 '18 at 11:49
  • 1
    In your code not really. You could write a def macro, that would take the called body and its string representation and pass it to function, but otherwise no. But that's a big topic, not suitable for discussion in comments ;) – Mateusz Kubuszok Feb 06 '18 at 11:52
  • `fn` could be anonymous ... i.e. not have a name at all. – Dima Feb 06 '18 at 12:04
1

You can have multiple parameter lists in function declaration. It is mostly the same as merging all the parameters into one list (def foo(a: Int)(b: Int) is more or less equivalent to def foo(a: Int, b: Int)) with a few differences:

  • You can reference parameters from previous list(s) in declaration: def foo(a : Int, b: Int = a + 1) does not work, but def foo(a: Int)(b: Int = a +1) does.

  • Type parameters can be inferred between parameter lists: def foo[T](x: T, f: T => String): String ; foo(1, _.toString) doesn't work (you'd have to write foo[Int](1, _.toString), but def foo[T](x: T)(f: T => String); foo(1)(_.toString) does.

  • You can only declare the entire list as implicit, so, multiple lists are helpful when you need some parameters to be implicit, and not the others: def foo(a: Int)(implicit b: Configuration)

Then, there are some syntactical advantages - things you could do with the single list, but they'd just look uglier:

  • Currying:

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

    You could write this with the single list too, but it wouldn't look as nice:

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

Finally, to your question:

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

is nice, because parenthesis are optional around lists with just a single argument. So, this lets you write things like

retry(3) { 
  val c = createConnection
  doStuff(c)
  closeConnection(c)
}

which would look a lot uglier if f was merged into the same list. Also, currying is useful:

val transformer = retry[Int](3)

Seq(1,2,3).map { n => transformer(n + 1) }
Seq(4,5,6).map { n => transformer(n * 2) }
Dima
  • 39,570
  • 6
  • 44
  • 70
0

If you want to pass a callable function to any function should you always use a round brackets next to the "list" of optional parameter? Why not put together with the parameters?

There is no such obligation, it's (mostly) a matter of style. IMO, it also leads to cleaner syntax. With two parameter lists, where the second one is the function yielding the result, we can call the retry method:

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

Instead, of we used a single parameter list, our method call would look like this:

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

I find the first syntax cleaner, which also allows you to use retry as a curried method when only supplying the first parameter list

However, if we think of a more advanced use case, Scala type inference works between parameter lists, not inside them. That means that if we have a method:

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

We would be able to call our method without specifying the type of T or U at the call site:

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

But if we used a single parameter list,

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

This won't compile:

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

Instead, we'd need to write:

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

To aid the compiler at choosing the right types.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321