Я так делал интерпретатор лиспа - в ридере лежат все объявленные переменные, а при запуске лямбды мы через local изменяем окружение ридера на то, которое нужно этой самой лямбде для работы, т.е. захваченные ей переменные.
. для "ридер" (на самом деле монад) это <=< (и его перевернутая версия >=>) (.) :: (b -> c) -> (a -> b) -> a -> c (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c