It may be better to consider TVar
not a variable, but a channel you can read from and write to.
A pure variable may be considered a pure function which always returns some value (and this value is bound only once, like in your example).
A variable/function in a monad has some "context" (that's what monads are for), that may change its value (e.g. randomIO :: Random a => IO a
from System.Random
may be considered a "monadic value", value, which may be changed on any call).
Reading and writing to TVar
are explicit operations that are not pure, that's why functions readTVar
/writeTVar
are wrapped into STM
monad, they depend on some hidden context, which may change the result (making value transfer possible between threads). That bounds these operations into STM
monad though, which can be escaped to IO
only.