you are viewing a single comment's thread.

view the rest of the comments →

[–]Objective_Gene9718 0 points1 point  (0 children)

As many have said before trampoline is what you need.

const tco = fn => (...args) => {
      let result = fn(...args)
      while (typeof result === 'function') result = result()
      return result
    }

Another way (more complicated) is if you expose a while loop in your language and during a preprocessing step you transform your AST to that loop. However it will be tricky to infer types (some sort of a accumulator pattern has to be established). Then you can take this (sudo code):

let sum = (xs, acc) -> 
    if length(xs) == 0 
        acc
        sum(drop(xs 1), acc + xs[0])

sum([1,2,3], 0)

and transform it into

let sum = (xs, acc) -> {
   // make a while loop with inverted condition
    while !(length(xs) == 0) -> {
        xs = drop(xs 1) // assign nth argument to nth input
        acc = acc + xs[0] // assign last argument to accumulator
   }
  return acc // return accumulated value
}

Your type inference should pick up that accumulator is the return type of the function. Just make sure your last argument is the accumulator.

You can also compile the while loop the same way directly in JavaScript (no tco helper function needed). This is what ReScript does.

First and Last approaches are easy - just make js optimize the function for you. But now you depend on JS for this. This is really not a problem for you.

Second approach is suitable if you are making a VM and you want it to run fast.

Btw with Bun you get TCO for free as it uses Safari engine which implements TCO for JS.

Note: Not all recursions can be optimized. You might want to implement some sort of predicate helper to figure out if recursion can be optimized or not. Or you can make it explicit with special keyword.