you: "In haskell you can't rebind a binding to a new value the way you can in rust, because haskell is declarative. You would just get a recursive definition that would never terminate."
me, with the cheatcode to activate imperative haskell:
import Data.Functor.Identity
f = runIdentity $ do
x <- 5
y <- 6
x <- x + y
return x
main = do
print f
vi@localhost ~> runghc whatever.hs
11
Naturally, this optimizes perfectly with constant folding:
Main_main3_closure:
.quad ghczmbignum_GHCziNumziInteger_IS_con_info
.quad 11
Expanded explanation
So Identity is a basic newtype that just wraps a value, which among other things, gives you the Identity monad definition for whatever value. and thats nothing special, it just passes the wrapped value into the next chain. It's usually used in combination with other complicated shit like monad transformers. Not really important. The important thing for us is that it gives us imperative do syntax, that implicitly turns
do
x <- 5
return (x + 6)
into
5 >>= (\x -> return (x + 6))
Also because of fromIntegral reasons, the 5 can get automatically turned into an Identity 5.
ANYWAYS
Identity 5 >>= (\x -> return (x + 6)) takes the 5 out of the Identity, puts it into the function, which then wraps 11 in a new Identity.
newtypes like this are free as in codegen, they dont add any runtime overhead they just add type information for the typeclass system to work. So expanding the do-notation we have
f = runIdentity $
Identity 5 >>= (\x ->
Identity 6 >>= (\y ->
Identity (x + y) >>= (\x ->
return x
)
)
)
And expanded like this its easy to see WHY we can introduce a second x that shadows the first x, why it doesnt generate a recursive loop, and why this optimizes so well. When we fully inline >>= for Identity what we get is
f =
(\x ->
(\y ->
(\x -> x) (x + y)
) 6
) 5
and this is literally just single-static-assignment form. compilers love SSA, thats what they turn everything into anyway. We already have this at the highest levels of our code before we even get to LLVM or whatever. In fact, I doubt LLVM is even responsible for the constant folding here, haskell can easily do this just by variable substitution
f = (\y -> (\x -> x) (5 + y)) 6
f = (\x -> x) (5 + 6)
f = (\x -> x) 11
f = 11
