#haskell #functional-programming
#haskell #функциональное программирование
Вопрос:
В настоящее время я занимаюсь monad transformer и пытаюсь действительно понять сигнатуры типов, и у меня возникает некоторая путаница. Давайте используем следующий стек для обсуждения:
newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s IO) a }
Я пытаюсь проходить слой за слоем и записывать подписи развернутого типа, но застреваю:
newtype Stack s m a = Stack {
runStack :: ReaderT s (StateT s IO) a }
-- ReaderT s m a
-- s -> m a
-- s -> (StateT s IO) a
-- StateT s m a
-- s -> (s -> IO (a, s)) a
Это просто не похоже на действительную сигнатуру возвращаемого типа в последней строке, по сути, у нас есть функция, которая принимает s и возвращает функцию, сопоставленную с a
?
Я понимаю, что внутренняя функция в конечном итоге преобразуется в монаду, и именно поэтому это m
in ReaderT r m a
, но это напрягает мой мозг.
Может ли кто-нибудь предложить какое-либо представление, правильно ли я проанализировал типы, и я просто должен признать, что s -> (s -> IO (a, s)) a
это действительно так?
Спасибо
Комментарии:
1. У нас есть
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
. Поскольку это так,(StateT s IO) a
все сводится кs -> IO (a,s)
, и никаких зависаний нетa
.2. @duplode что насчет того,
a
что возвращает ReaderT?newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
. Потому чтоIO (a,s)
естьa
в кортеже, который был удален из крайнего правого, как у меня выше?3. «что насчет того,
a
что возвращает ReaderT?» — Этоa
в(StateT s IO) a
. Обратите внимание, чтоReaderT
это возвращает не ana
само по себе, а скорееm a
.4. @duplode : потрясающе: теперь это имеет полный смысл, вау, большое спасибо, что прояснили это для меня!!
Ответ №1:
Стек, который вы написали, немного странный, потому что он параметризован m
слева, но специализирован для IO
справа, поэтому давайте рассмотрим полностью m
параметризованный вариант:
newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s m) a }
Теперь runStack
здесь просто имя поля, поэтому мы можем удалить его и написать эквивалентное newtype
определение:
newtype Stack s m a = Stack (ReaderT s (StateT s m) a)
У нас также есть следующие определения нового типа библиотеки, пропускающие имена полей. Я также использовал новые переменные, чтобы мы не делали что-то глупое, например, не путали два a
s в разных областях при расширении:
newtype ReaderT r1 m1 a1 = ReaderT (r1 -> m1 a1)
newtype StateT s2 m2 a2 = StateT (s2 -> m2 (a2, s2))
Конечно, если нас интересует только тип с точностью до изоморфизма, тогда оболочки newtype не имеют значения, поэтому просто перепишите их как псевдонимы типов:
type Stack s m a = ReaderT s (StateT s m) a
type ReaderT r1 m1 a1 = r1 -> m1 a1
type StateT s2 m2 a2 = s2 -> m2 (a2, s2)
Теперь легко расширить Stack
тип:
Stack s m a
= ReaderT s (StateT s m) a
-- expand ReaderT with r1=s, m1=StateT s m, a1=a
= s -> (StateT s m) a
= s -> StateT s m a
-- expand StateT with s2=s m2=m a2=a
= s -> (s -> m (a, s))
= s -> s -> m (a, s)
Как отметил @duplode, здесь нет ничего лишнего a
.
Интуитивно это Stack
считывается из s
(первый аргумент), принимает начальное состояние типа s
(второй аргумент) и возвращает монадическое действие в m
(например, IO
), которое может возвращать значение типа a
и обновленное состояние типа s
.
Комментарии:
1. Ваш ответ был очень полезен, я действительно ценю это, спасибо!