Одна кодировка:
import Data.Char (toUpper, toLower)
newtype State = State { unState :: String -> IO State }
stateA :: State
stateA = State $ \name -> do
putStrLn (map toLower name)
return stateB
stateB :: State
stateB = go 2
where
go 0 = stateA
go n = State $ \name -> do
putStrLn (map toUpper name)
return $ go (n-1)
Не обманывайтесь IO
, это чистый перевод этого паттерна (мы не используем IORef
для хранения состояния или чего-либо еще). Расширяя newtype
, мы видим, что означает этот тип:
State = String -> IO (String -> IO (String -> IO (String -> ...
Он принимает строку, выполняет некоторые операции ввода-вывода, запрашивает другую строку и т. Д.
Это мое любимое кодирование шаблонов абстрактных классов в ОО: абстрактный класс -> тип, подклассы -> элементы этого типа.
Декларация newtype State
заменяет абстрактную декларацию writeName
и ее подпись. Вместо того, чтобы передавать StateContext
, в который мы назначаем новое состояние, мы просто возвращаем новое состояние. Внедрение возвращаемого значения в IO
говорит о том, что новое состояние может зависеть от ввода-вывода. Поскольку в этом примере это не является технически необходимым, мы могли бы использовать более строгий тип
newtype State = State { unState :: String -> (State, IO ()) }
, в котором мы все еще можем выразить это вычисление, но последовательность состояний фиксирована и не может зависеть от ввода. Но давайте придерживаться оригинального, более снисходительного типа.
А для "тестового клиента":
runState :: State -> [String] -> IO ()
runState s [] = return ()
runState s (x:xs) = do
s' <- unState s x
runState s' xs
testClientState :: IO ()
testClientState = runState stateA
[ "Monday"
, "Tuesday"
, "Wednesday"
, "Thursday"
, "Saturday"
, "Sunday" ]