In chapter 7 of the very nice Write yourself a Scheme in 48 hours an environment for handling variables in the interpreted language (Scheme, where variables are mutable) is introduced as
type Env = IORef [(String, IORef LispVal)]
-- where LispVal is a simple type representing a lisp value:
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool
non tl;dr (tl;dr below)
There is a reason given why the State monad isn't a good choice (environments will get passed around and eventually need to exist 'outside' of runState) so IORef is chosen instead. However, I wondered why one wouldn't just represent as an environment with Map String LispVal? In my (limited) understanding about how GHC compiles our programs if we later changed a variable with Map.insert that shouldn't copy the entire map at all. So is the mutability of IORef here really needed? In other words, is it obvious that IORef is the way to go here? Moreover, I had the impression that IORef was best to be avoided if possible and a typical use case was to 'leak' values from something like a callback in a foreign framework.
I struggled to come up with a good experiment on comparing performance between the two alternatives, though. It seems that the GHC optimizes my deterministic repeated calls too well.
tl;dr
- Is it that obvious to use
IORefin the example above, both in terms of performance and other caveats (mutability, IO in code that would otherwise be pure)? - Is using
Map String LispVala bad choice here if we expect frequent changes usinginsertorinsertWith?