在写网络监控脚本的时候,有时候需要处理嵌套的日志结构或者层层调用的请求链。这时候普通的循环不太够用,就得靠函数自己调用自己——也就是递归。Scala 作为一门函数式编程语言,对递归的支持特别友好,写起来也挺清爽。
递归函数的基本写法
Scala 中写递归函数,最关键的是要定义好终止条件,不然函数会一直调下去,直到爆栈。比如我们想算一个数的阶乘,可以这么写:
def factorial(n: Int): Int = {
if (n <= 1) 1
else n * factorial(n - 1)
}
这个函数的意思是:如果 n 小于等于 1,就返回 1,否则就用当前的 n 乘以 factorial(n-1) 的结果。每调一次,n 都在变小,最终会走到 1,结束递归。
用递归遍历树形日志结构
假设你在监控一个微服务系统,每个请求会生成一个请求节点,节点里可能包含多个子请求。这种数据天生就是树状结构。我们可以用递归来一层层钻进去,把所有耗时超过阈值的请求找出来。
case class Request(id: String, duration: Long, children: List[Request])
def findSlowRequests(req: Request, threshold: Long): List[String] = {
val current = if (req.duration > threshold) List(req.id) else List()
val fromChildren = req.children.flatMap(child => findSlowRequests(child, threshold))
current ++ fromChildren
}
这段代码会先判断当前节点是否慢,再递归处理所有子节点,最后把所有超时的请求 ID 汇总起来。整个过程像扫地机器人走迷宫,不漏掉任何一个角落。
尾递归优化避免栈溢出
普通递归在数据很深的时候容易导致 StackOverflowError。Scala 提供了尾递归优化,只要把递归调用放在函数的最后一步,编译器就能把它转成循环,避免爆栈。
还是拿阶乘举例,改写成尾递归版本:
def factorialTail(n: Int, acc: Int = 1): Int = {
if (n <= 1) acc
else factorialTail(n - 1, n * acc)
}
这里多了一个参数 acc,用来累积结果。每次递归都把当前结果传下去,最后一刻直接返回,没有额外计算。这样的写法更安全,适合处理大量数据。
在实际做网络请求追踪时,如果调用链特别深,推荐优先使用尾递归,避免程序因为递归太深突然挂掉。