Предпочитая монаду Reader по прямой передаче среды в качестве параметров

Я писал базовое приложение CRUD на Haskell, используя библиотеки Servant и Opaleye.

Слуга для настройки конечных точек API и Opaleye для хранения данных в БД.

Допустим, есть конечная точка GET /users который возвращает список всех пользователей из БД и другой конечной точки POST /user который создает нового пользователя и сохраняет его в БД.

Программа создается установлением соединения с БД, а затем передаёт это соединение в качестве параметра этим функциям конечных точек API (настройка с использованием Servant) в качестве параметра.

Кто-то порекомендовал мне, что лучше использовать Reader Monad и хранить соединение в среде.

Я смог это сделать, но я не понимаю, почему Reader Monad является предпочтительным способом совместного использования среды, а не прямой передачи аргументов.

PS - Будучи новичком в Haskell, я могу использовать Monads, следовать учебным пособиям и запускать мою программу, но я действительно не знаю, какая скрытая математика стоит за ними. Вот почему я хочу избегать использования монад (до тех пор, пока я полностью не пойму идею, лежащую в основе монад).

Вот мой код, кстати.

1 ответ

Решение
  1. Monad Reader более удобен, когда вы хотите передавать аргументы на несколько уровней глубже в стек вызовов.

  2. Monad Reader облегчает изменение / расширение кода. Предположим, вы хотите получить какое-то значение типа Foo из базы данных, обновить его (нечистым образом) и сохранить его обратно. Вот две версии, с Reader и с явной передачей аргументов.

    data Foo = ...
    modifyFoo :: Foo -> IO Foo
    type Handler a = Reader Connection IO a
    
    fetch1 :: Connection -> Int -> IO Foo
    fetch2 :: Int -> Handler Foo
    
    store1 :: Connection -> Foo -> IO ()
    store2 :: Foo -> Handler ()
    
    modify1 :: Connection -> Int -> IO ()
    modify1 conn key = do
      prev <- fetch1 conn key
      new  <- modify prev
      store1 conn new
    
    modify2 :: Int -> Handler ()
    modify2 key = do
      prev <- fetch2 key
      new  <- liftIO $ modify prev
      store2 new
    
    -- for brave souls
    modify2' :: Int -> Handler ()
    modify2' = fetch2 >=> liftIO . modify >=> store2
    

Должен ли когда-нибудь fetch2 а также store2 измените аргумент с Connection на что-то другое (или большее), вы просто обновите Handler введите псевдоним, modify2 остается такой же. В случае modify1, Connection явным образом указывается в сигнатуре типа, вам также придется ее изменить.

Для другого примера использования ReaderЯ бы предложил xmonad оконный менеджер. Есть XConfig тип данных где-то во внутренних органах X монада, но большую часть времени я не хочу это знать, оставь в покое.

Другие вопросы по тегам