Как скрыть состояние от функций, вызывающих другие функции, использующие это состояние - PullRequest
0 голосов
/ 20 октября 2018

Я хотел бы, чтобы некоторые функции более высокого уровня в моей программе на Haskell вызывали другие функции, которые в конечном итоге вызывают функции, которые используют некоторое состояние или конфигурацию, и не должны передавать состояние вокруг всех этих вызовов функций.Я понимаю, что это классическое использование государственной монады (или, возможно, монады Reader?).

(я также не уверен, должен ли StateT (как в моем примере ниже) включить выполнение ввода-вывода или результаты должны как-то выводиться отдельно.)

На этом этапе IЯ довольно смущен всеми учебниками, сообщениями в блогах и подобными вопросами здесь, и не могу выбрать решение.Или я неправильно понял скрывающую вещь?

Вот небольшой пример:

import Control.Monad.State

-- Here's a simple configuration type:
data Config = MkConfig {
      name :: String
    , num  :: Int
    } deriving Show

-- Here's a couple of configurations.
-- (They're hard coded and pre-defined.)
c1 = MkConfig "low" 7
c2 = MkConfig "high" 10

-- Here's a lower level function that explicitly uses the config.
-- (The String is ignored here for simplicity, but it could be used.)
fun :: Config -> Int -> Int
fun (MkConfig _ i) j = i*j

-- testA and GoA work fine as expected.
-- fun uses the different configs c1,c2 in the right way.
testA = do
    a <- get
    lift (print (fun a 2))
    put c2
    a <- get
    lift (print (fun a 4))

goA = evalStateT testA c1
-- (c1 could be put at the start of testA instead.)

-- But what I really want is to use fun2 that calls fun, 
-- and not explicitly need state.
-- But this function definition does not compile:
fun2 :: Int -> Int
fun2 j = 3 * fun cf j  
-- fun needs a config arg cf, but where from?

-- I would like a similar way of using fun2 as in testB and goB here.
testB = do
    a <- get
    lift (print (fun2 3))  -- but fun2 doesn't take the state in a 
    put c2
    a <- get
    lift (print (fun2 42))  -- but fun2 doesn't take the state in a 

goB = evalStateT testB c1 

Я хочу скрыть конфигурацию от функций более высокого уровня, таких как fun2, в моей программе, сохраняя при этомВозможность изменения конфигурации и запуска этих функций с новой конфигурацией.Это вопрос «как это сделать?» (Если только у меня нет полной идеи).

1 Ответ

0 голосов
/ 20 октября 2018

Конечно, вы не можете «скрыть конфигурацию» в сигнатуре типа: простая старая функция Int -> Int должна быть прозрачной по ссылкам, и поэтому она не может зависеть или принимать какое-либо значение Config.

Что вы, вероятно, хотите сделать, это что-то вроде:

fun2 :: Int -> State Config Int    -- An `Int -> Int` that depends on `Config` state.
                                   -- Compare to how `Int -> IO Int` is like an
                                   -- `Int -> Int` function that depends on IO.
fun2 j = do
  c1 <- get
  return (3 * fun c1 j)

И тогда, где бы у вас не было c :: Config, вы можете получить результат, например,

let result = evalState (fun2 42) c    -- An Int.

См. Также Комбинация StateT IO и State :

hoistState :: Monad m => State s a -> StateT s m a
hoistState = StateT . (return .) . runState

Тогда вы можете написать что-то вроде

testB :: StateT Config IO ()
testB = do
    -- Fancy:
    result <- hoistState (fun2 42)

    -- Equivalent:
    c <- get
    let result' = evalState (fun2 42) c

    lift (print (result, result'))
...