19

I have a case where I want to call a method n times, where n is an Int. Is there a good way to do this in a "functional" way in Scala?

case class Event(name: String, quantity: Int, value: Option[BigDecimal])

// a list of events
val lst = List(
    Event("supply", 3, Some(new java.math.BigDecimal("39.00"))),
    Event("sale", 1, None),
    Event("supply", 1, Some(new java.math.BigDecimal("41.00")))
    )

// a mutable queue
val queue = new scala.collection.mutable.Queue[BigDecimal]

lst.map { event =>
    event.name match {
        case "supply" => // call queue.enqueue(event.value) event.quantity times
        case "sale" =>   // call queue.dequeue() event.quantity times
    }
}

I think a closure is a good solution for this, but I can't get it working. I have also tried with a for-loop, but it's not a beautiful functional solution.

Jonas
  • 121,568
  • 97
  • 310
  • 388

5 Answers5

41

The simplest solution is to use range, I think:

(1 to n) foreach (x => /* do something */)

But you can also create this small helper function:

implicit def intTimes(i: Int) = new {
    def times(fn: => Unit) = (1 to i) foreach (x => fn)
}

10 times println("hello")

this code will print "hello" 10 times. Implicit conversion intTimes makes method times available on all ints. So in your case it should look like this:

event.quantity times queue.enqueue(event.value) 
event.quantity times queue.dequeue() 
tenshi
  • 26,268
  • 8
  • 76
  • 90
  • Thanks, but I couldn't use the first suggestion, since my `queue.dequeue()` return a `BigDecimal`. – Jonas Sep 23 '11 at 16:55
  • 3
    `for (i <- 1 to n) {/* do something */}` is slightly shorter than `foreach` :) – Luigi Plinge Sep 23 '11 at 23:59
  • @Luigi Plinge: I don't think that 4 extra characters make much difference, but I agree, that for comprehension looks a little bit nicer :) – tenshi Sep 24 '11 at 00:24
  • how could I modify `times` to include `i` as a parameter to the `fn`? Example: let's say I wanted to print `"hello" + i` where i = 1 ... 10 in your example – Kevin Meredith Sep 28 '13 at 15:08
  • @Kevin You can rewrite `times` function like this: `def times(fn: Int => Unit) = (1 to i) foreach fn`. The usage will now look like this: `10 times (i => println("hello" + i))`. So instead of taking by-name parameter, `times` now takes `Int => Unit` function as an argument. – tenshi Sep 28 '13 at 15:37
13

Not quite an answer to your question, but if you had an endomorphism (i.e. a transformation A => A), then using scalaz you could use the natural monoid for Endo[A]

N times func apply target

So that:

scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._

scala> Endo((_:Int) * 2).multiply(5)
res3: scalaz.Endo[Int] = Endo(<function1>)

scala> res1(3)
res4: Int = 96
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
12

A more functional solution would be to use a fold with an immutable queue and Queue's fill and drop methods:

 val queue = lst.foldLeft(Queue.empty[Option[BigDecimal]]) { (q, e) =>
   e.name match {
     case "supply" => q ++ Queue.fill(e.quantity)(e.value)
     case "sale"   => q.drop(e.quantity)
   }
 }

Or even better, capture your "supply"/"sale" distinction in subclasses of Event and avoid the awkward Option[BigDecimal] business:

sealed trait Event { def quantity: Int }
case class Supply(quantity: Int, value: BigDecimal) extends Event
case class Sale(quantity: Int) extends Event

val lst = List(
  Supply(3, BigDecimal("39.00")),
  Sale(1),
  Supply(1, BigDecimal("41.00"))
)

val queue = lst.foldLeft(Queue.empty[BigDecimal]) { (q, e) => e match {
  case Sale(quantity)          => q.drop(quantity)
  case Supply(quantity, value) => q ++ Queue.fill(quantity)(value)
}}

This doesn't directly answer your question (how to call a function a specified number of times), but it's definitely more idiomatic.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
10
import List._

fill(10) { println("hello") }

Simple, built-in, and you get a List of Units as a souvenier!

But you'll never need to call a function multiple times if you're programming functionally.

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 3
    "But you'll never need to call a function multiple times if you're programming functionally." << Huh? – missingfaktor Sep 24 '11 at 09:42
  • How would I add four numbers to a List without calling the "add"-function four times? – Jonas Sep 24 '11 at 11:08
  • @Jonas Apocalisp is right in his comment that if you're looking for functional code, you shouldn't use mutable data structures, which by their nature require imperative manipulation. (That's not to say that mutable isn't the best solution - sometimes it will be. But it's not functional style.) With immutable structures the answer is basically a) recursion, b) use `flatMap` in conjuntion with `List.fill` or `(1 to n).map...`, or c) use a for-expression to do this more legibly. @missingfaktor I mean within a given scope using the same arguments, it makes no sense – Luigi Plinge Sep 25 '11 at 23:00
  • @Luigi: It does make sense, since it's the only way to add multiple items to a queue, as in this case. – Jonas Sep 25 '11 at 23:28
  • 1
    @Jonas A method that has side-effects like mutating a queue isn't a function - it's an imperative procedure. You're asking how to do something imperative - adding items to a mutable queue - using functions. Since functions by definition always return the same thing for given arguments, and don't have side-effects, this is not possible. Using an immutable queue will help you appreciate this. – Luigi Plinge Sep 26 '11 at 12:59
  • @Luigi: My problem can be solved both imperative and functional (by iteration or recursion). Yes, if you do this in a functional way, you call the function with different arguments (the state), but the function still has to be called **multiple** times. See the accepted answer for a good functional solution. – Jonas Sep 26 '11 at 13:11
  • @Jonas Yes the accepted answer shows how to implement the intention of your method functionally, but as the answerer says, it doesn't answer your question directly insofar as it doesn't work by just calling a method n times. The solution to your problem, and "how to call a method n times" are different things! – Luigi Plinge Sep 26 '11 at 13:52
5

With recursion:

def repeat(n: Int)(f: => Unit) { 
  if (n > 0) {
    f
    repeat(n-1)(f)
  }
}

repeat(event.quantity) { queue.enqueue(event.value) }
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487