Local Definitions
To define local names in an expression let-in is used:
fastExp :: Integer -> Integer -> Integer
-- fast exponentiation
fastExp _ 0 = 1
fastExp x n =
let y = fastExp x n_halved
n_halved = div n 2
in if even n
then y * y
else y * y * x
The indentation of where defines its scope.
All definitions in a where / let block can refer to each other without order restrictions. Itβs like they live in the same little bubble of βdefined together.β
fasterExp :: Integer -> Integer -> Integer
fasterExp _ 0 = 1
fasterExp x n
| even n = y * y
| otherwise = y * y * x
where
y = fasterExp x n_halved
n_halved = div n 2
Yes, in Haskell y and n_halved are simultaneously defined, so the order doesn't matter. This is what lazy evaluation lets you do, nothing actually runs until it's needed, so Haskell doesn't care about the textual order, only about dependency.
- When evaluating
y, it looks upn_halved(which is already known, because itβs in the samewhereblock). - This is possible because
n_halvedis a pure expression (div n 2), no side effects, no mutations.
Here, I started to feel confused, so I tried to write the same function in C to understand better:
#include <stdio.h>
long long int fasterExp(int x, int n) {
if (n == 0)
return 1;
int n_halved = n / 2;
long long int y = fasterExp(x, n_halved);
return (n % 2 == 0) ? y * y : y * y * x;
}
int main() {
printf("%lld", fasterExp(3, 5));
return 0;
}
- Compared to that, C expects sequential evaluation and declarations before use. It scans top-to-bottom, so you can't use
n_halvedbefore declaring it. - Also, C variables are mutable, so compiler and runtime need to respect order for correctness.