scala-arm
scala-arm copied to clipboard
ManagedResource.map() is not stack safe
Greetings,
I have been using scala-arm to read and concatenate lines from a large number of files, an operation that requires a large number of nested calls to ManagedResource.map(). This appears to trigger a stack overflow at somewhere between 1,000 and 10,000 nested calls. Here is a trivial reproduction:
import resource._
import scala.math.BigInt._
// calculate factorial inside of Scala Option
def factorialO(max: Int): Option[BigInt] = {
val z: Option[BigInt] = Option(1)
(1 to max).foldLeft(z) { (b,a) => b.map(_ * a) }
}
// calculate factorial inside of ManagedResource
def factorialR(max: Int): ManagedResource[BigInt] = {
val z: ManagedResource[BigInt] = new ConstantManagedResource(1)
(1 to max).foldLeft(z) { (b,a) => b.map(_ * a) }
}
// Some(93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000)
println(factorialO(100))
// 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
println(factorialR(100).acquireAndGet(x => x))
// prints really big number
println(factorialO(10000))
// java.lang.StackOverflowError
//println(factorialR(10000).acquireAndGet(x => x))
It seems the solution is to define map() and flatMap() using trampolines like is done in Scala Cats, or am I missing something?
All the best, -underspecified
Yes, we'd need to trampoline to fix this. Will leave this as an open implementation question for now. This library was not intended for such nested usage initially, but if it becomes a common use-case we must support, we can adapt to it.
import scala.math.BigInt def fact(x:BigInt):BigInt={ if(x==0) 1 else x * fact(x-1) } fact: (x: scala.math.BigInt)scala.math.BigInt fact(100000)
scala> fact(100000) java.lang.StackOverflowError at scala.math.BigInt$.apply(BigInt.scala:42) at scala.math.BigInt$.int2bigInt(BigInt.scala:100) at scala.math.BigInt.isValidInt(BigInt.scala:137) at scala.math.ScalaNumericAnyConversions.unifiedPrimitiveEquals(ScalaNumericConversions.scala:117) at scala.math.ScalaNumericAnyConversions.unifiedPrimitiveEquals$(ScalaNumericConversions.scala:113) at scala.math.BigInt.unifiedPrimitiveEquals(BigInt.scala:114) at scala.math.BigInt.equals(BigInt.scala:132) at scala.runtime.BoxesRunTime.equalsNumNum(BoxesRunTime.java:170) at scala.runtime.BoxesRunTime.equalsNumObject(BoxesRunTime.java:142)
Any solution to avoid this execption