Are there any drawbacks of the alternative (state-based) approach?
Well given you feed the function state variables, you interpreter/program will have to push these on the call stack, so as a result the time it takes to make a call will be larger and the call stack will grow faster (given no tail-recursion is used). Even if tail recursion is used, the stack frame will be larger and thus require more time (in case of tail recursion, you overwrite the stackframe; but it will of course take time to overwrite it).
Usually when you call, the call stack looks like:
+----------------+
| return address |
+----------------+
| a |
| b |
+----------------+
With a
and b
parameters, now given you make a call without tail recursion if you make a call, it will push, on the stack, so the stack will look like:
+----------------+
| return address'|
+----------------+
| a' |
| b' |
+----------------+ +----------------+
| return address | --> | return address |
+----------------+ +----------------+
| a | | a |
| b | | b |
+----------------+ +----------------+
Now if you use tail recrusion, the call stack is not pushed, but its parameters are overridden:
+----------------+ +----------------+
| return address | --> | return address | (no new return address)
+----------------+ +----------------+
| a | | a' |
| b | | b' |
+----------------+ +----------------+
Note however that a
and b
on the call stack are modified before the jump is made. If you have more parameters (because of state parameters), these will of course all have to update and thus cost time.
Does it violate any conditions of a pure functional implementation, e.g., regarding mutable variables?
No you do not mutate any variables here: you pass a state through parameters, so every variable is set only once since the variables in the call are different from the one you receive.
I realize that the state-based approach cannot benefit from tail call optimization, but I believe that is irrelevant since Python does not support such optimization anyway.
Python indeed does not support tail recursion. It will even be hard to support this since Python is a dynamic programming language and you could thus change the function pointer while you run the function resulting in a different call.
Two final important notes:
- your
_canon
version actually cannot use tail-recursion since the multiplication of the result needs to be done, so the call stack has to unwind iteratively. Your _alter
version however could, by some programming languages be implemented with tail-recursion; and
- your program does not calculate the Fibonacci sequence, but the factorial.