Я знаю, как использовать это, но я не понимаю точно, как это делает (Монада Читателя)

Рассмотрим следующий код (с пропущенными очевидными частями)

main = do

    let s = "123456";
    let len = runReader calculateContentLength s
    putStrLn $ "Original 's' length: " ++ (show len)


calculateContentLength :: Reader String Int
calculateContentLength = do
    content <- ask           -- this seems to be the same as 'reader id'
    return (length content);

Как "ask" попадает в строковый параметр? Насколько я понимаю, из-за объявления типа

calculateContentLength :: Reader String Int

у функции 'convertContentLength' есть тип возвращаемого значения (типа Reader String Int), но у него нет входящих аргументов. Я понимаю, что сама функция - это просто один из двух аргументов, передаваемых в функцию runReader, но как именно второй параметр runReader, 's', привязан к 'ask' внутри 'convertContentLength'?

Другими словами, каким образом "calcContentLength" "знает" (и получает доступ) второй аргумент, передаваемый с помощью "runReader"?

1 ответ

Решение

Давайте посмотрим на один способ определить Reader,

newtype Reader r a = Reader { runReader :: r -> a }

Так Reader это конструктор, который принимает функцию. Эта функция принимает среду типа rи возвращает результат типа a,

ask = Reader { runReader = \env -> env }
ask = Reader id

return Операция просто игнорирует среду и возвращает значение.

return x = Reader { runReader = \_ -> x }

m >>= n операция выполняет простую последовательность: она принимает среду, запускает m в этой среде, а затем работает n в той же среде, передавая результат m,

m >>= n = Reader $ \env -> let
  a = runReader m env
  in runReader (n a) env

Так что теперь мы можем взять ваш пример, десагар и постепенно его уменьшить.

calculateContentLength = do
  content <- ask
  return (length content)

-- substitute definition of 'ask'

calculateContentLength = do
  content <- Reader id
  return (length content)

-- substitute definition of 'return'

calculateContentLength = do
  content <- Reader id
  Reader (\_ -> length content)

-- desugar 'do' into '>>='

calculateContentLength =
  Reader id >>= \content -> Reader (\_ -> length content)

-- definition of '>>='

calculateContentLength = Reader $ \env -> let
  a = runReader (Reader id) env
  in runReader ((\content -> Reader (\_ -> length content)) a) env

-- reduce lambda

calculateContentLength = Reader $ \env -> let
  a = runReader (Reader id) env
  in runReader (Reader (\_ -> length a)) env

-- definition of 'runReader'

calculateContentLength = Reader $ \env -> let
  a = id env
  in runReader (Reader (\_ -> length a)) env

-- definition of 'id'

calculateContentLength = Reader $ \env -> let
  a = env
  in runReader (Reader (\_ -> length a)) env

-- remove redundant variable

calculateContentLength = Reader $ \env
  -> runReader (Reader (\_ -> length env)) env

-- definition of 'runReader'

calculateContentLength = Reader $ \env -> (\_ -> length env) env

-- reduce

calculateContentLength = Reader $ \env -> (length env)
calculateContentLength = Reader length

Теперь должно быть легче увидеть, как runReader calculateContentLength так же, как просто length, и как ask не волшебно - монада >>= Операция строит функцию, которая неявно передает среду для вас, когда вы запускаете вычисления с runReader,

В действительности, Reader определяется с точки зрения ReaderT, который использует монадические действия вместо чистых функций, но форма его реализации по сути та же.

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