Почему преобразователь является первым аргументом "запускаемых" функций?
Я имею в виду, почему это не приходит последним?
Из-за этого соглашения для оценки стека трансформаторов нужно написать такую неловкую вещь:
runStateT (runReaderT (runRWST stack r s) r') s'
вместо:
runStateT s' $ runReaderT r' $ runRWST r s $ stack
И сочетая это с немедленным do
становится все более неловко
let
action = do
liftIO $ putStrLn "Do"
liftIO $ putStrLn "something"
in runStateT (runReaderT (runRWST action r s) r') s'
вместо:
runStateT s' $ runReaderT r' $ runRWST r s $ do
liftIO $ putStrLn "Do"
liftIO $ putStrLn "something"
Есть ли у этого мотивация или это просто неудачное соглашение?
Кстати, я понимаю, что текущее соглашение облегчает реализацию функции "run" с использованием синтаксиса записи, но это не может быть аргументом, поскольку библиотеки должны предпочесть удобство использования легкости реализации.
3 ответа
В записи HaskellWiki для порядка параметров и в этом вопросе о переполнении стека есть две рекомендации для порядка параметров:
- "Самый изменчивый" аргумент обычно должен идти последним.
- Поместите структуру данных в последнюю очередь.
По моему опыту с монадой Reader/State, одни и те же вычисления выполняются в разных средах / состояниях чаще, чем разные монадические вычисления в одной среде / состоянии. Текущий порядок параметров удобен для этого.
Другая причина: монадические значения Reader/State ведут себя очень похоже на функции, которые принимают параметры среды / начального состояния в качестве параметров. А в Haskell функции идут перед параметрами.
Чтобы избежать необходимости вложенных ReaderT
/StateT
/RWST
трансформаторы, вы можете работать с одним RWST
трансформатор, несущий глобальное состояние / окружающую среду, и использование zoom
а также magnify
из библиотеки линз для адаптации вычислений, которые работают в более ограниченных средах.
Я уверен, что есть элемент целесообразности. Это очень легко определить
newtype StateT = StateT { runStateT :: s -> m (a, s) }
и покончим с этим.
Я хотел бы добавить последовательность как ответ на соединение. Это все домыслы, так что возьмите это с крошкой соли:
Первоначальные реализации использовали синтаксис записи нового типа для записи этих определений данных, поэтому данные запускаются первыми при их запуске, не обращая внимания на то, является ли это предпочтительным по сравнению с данными, поступающими в качестве последнего аргумента. Более новые типы данных просто следуют этому соглашению.