You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Right now, recursive function are not supported. This is because recursive function are tricky to implement in a type system. One of the main problems is that they are very hard to type check. Let's look at an example:
def fact(n: int(0..)) = match n { 0 => 1, _ as x => fact(x - 1) * x };
This is a very simple example of a factorial function for unsigned integers. The problem is that x is the type int(1..), so x-1 is int(0..) which is the same as the input type. So to determine the output type of fact(int(0..)), we need to know the output type of fact(int(0..)).
One way to solve this would to examine the call stack and detect cycles. Suppose we want to evaluate fact(int(2..)). How would the call stack look like?
So we see a cycle of length 1 for fact(int(0..)). One way to resolve the cycle is to iteratively refine the output type until it converges.
We define never as the starting output type of fact(int(0..)). We now replace all recursive calls to fact(int(0..)) with never, so we get the expression match n { 0 => 1, _ as x => never * x }.
This evaluates to 1 which will be our next output type. The process repeats with match n { 0 => 1, _ as x => 1 * x }.
This evaluates to int(1..) which will be our next output type. The process repeats with match n { 0 => 1, _ as x => int(0..) * x }.
This evaluates to int(1..). This is the same type as in the previous iteration, so the output type of fact(int(0..)) has successfully converged to int(1..).
Since we now know the output type of fact(int(0..)), it is trivial to determine the output types of fact(int(1..)) and fact(int(2..)).
However, even determining the output type of a recursive function as simple as this, isn't easy to implement. Factorial is also a simple example considering that it (1) has a cycle of length 1 and (2) also recurses into itself once per invocation.
Let's look at a recursive implementation of the Fibonacci function for a more complex example:
def fib(n: int(0..)) = match n { 0 | 1 => 1, _ as x => fib(x - 1) + fib(x - 2) };
Since this function has exponential time complexity with memoization, let's assume that we implemented efficient memoization (or maybe it should be called call de-duplication?). Let's see the de-duped call stack for fib(int(4..)):
The length-one cycle of fib(int(0..)) can be resolved just like with fact(int(0..)), so let's try that:
We start with never again, so we get match n { 0 | 1 => 1, _ as x => never + never }.
This evaluates to 1. The pattern is pretty obvious again, so let's just list out the next couple of iteration output types:
Next is int(1..2) (so 1 or 2).
Next is int(1..4).
Next is int(1..8).
Next is int(1..16).
Next is int(1..32).
We can see a pattern here, but this is bad news. While the output type will clearly converge to int(1..inf)eventually, it will only do so after (mathematically speaking) infinitely many iterations. This is a problem.
The text was updated successfully, but these errors were encountered:
Right now, recursive function are not supported. This is because recursive function are tricky to implement in a type system. One of the main problems is that they are very hard to type check. Let's look at an example:
This is a very simple example of a factorial function for unsigned integers. The problem is that
x
is the typeint(1..)
, sox-1
isint(0..)
which is the same as the input type. So to determine the output type offact(int(0..))
, we need to know the output type offact(int(0..))
.One way to solve this would to examine the call stack and detect cycles. Suppose we want to evaluate
fact(int(2..))
. How would the call stack look like?So we see a cycle of length 1 for
fact(int(0..))
. One way to resolve the cycle is to iteratively refine the output type until it converges.never
as the starting output type offact(int(0..))
. We now replace all recursive calls tofact(int(0..))
withnever
, so we get the expressionmatch n { 0 => 1, _ as x => never * x }
.1
which will be our next output type. The process repeats withmatch n { 0 => 1, _ as x => 1 * x }
.int(1..)
which will be our next output type. The process repeats withmatch n { 0 => 1, _ as x => int(0..) * x }
.int(1..)
. This is the same type as in the previous iteration, so the output type offact(int(0..))
has successfully converged toint(1..)
.Since we now know the output type of
fact(int(0..))
, it is trivial to determine the output types offact(int(1..))
andfact(int(2..))
.However, even determining the output type of a recursive function as simple as this, isn't easy to implement. Factorial is also a simple example considering that it (1) has a cycle of length 1 and (2) also recurses into itself once per invocation.
Let's look at a recursive implementation of the Fibonacci function for a more complex example:
Since this function has exponential time complexity with memoization, let's assume that we implemented efficient memoization (or maybe it should be called call de-duplication?). Let's see the de-duped call stack for
fib(int(4..))
:The length-one cycle of
fib(int(0..))
can be resolved just like withfact(int(0..))
, so let's try that:never
again, so we getmatch n { 0 | 1 => 1, _ as x => never + never }
.1
. The pattern is pretty obvious again, so let's just list out the next couple of iteration output types:int(1..2)
(so 1 or 2).int(1..4)
.int(1..8)
.int(1..16)
.int(1..32)
.We can see a pattern here, but this is bad news. While the output type will clearly converge to
int(1..inf)
eventually, it will only do so after (mathematically speaking) infinitely many iterations. This is a problem.The text was updated successfully, but these errors were encountered: