если весь код это IO, просто еще ридер сверху для контекста, то можно таскать его явно, но геморно. Тогда можно просто таскать его неявно
type WIthEnv = (?env :: Env)
getX :: WithEnv => Int
getX = getXFromEnv ?env
action :: WithEnv => IO Int
action = do
action2
pure getX
action2 :: WithEnv => IO ()
action2 = do
print getX
main = do
let ?env = makeEnv ...
x <- action
...