О выборе места применения монадного параметра монадного преобразователя

Возьмем монадный преобразователь MaybeT:

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

Я бы не ожидал другого определения, потому что Maybe — это просто своего рода блок с (необязательным) содержимым (типа a выше), так что же MaybeT может делать с параметром m, если не обертывать все Maybe в нем? Написание :: Maybe (m a) не имело бы смысла, поскольку это всего лишь тип someaction <$> theMaybe.

Но в других случаях мог быть и другой выбор.

Возьмем монадный преобразователь StateT, определяемый как

newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }

Это определение, если я посмотрю на него в свете определения монады State (как это было бы, если бы оно не было определено поверх StateT через Identity, очевидно),

newtype State s a = State { runState :: s -> (a, s) }

кажется, соответствует интерпретации «функции a -> b как контейнера с содержимым b», именно так я изначально понял, что означает экземпляр Functor(->) r. Итак, справедливо: m оборачивает «содержимое» функции (т. е. «содержимое» вычислений с сохранением состояния).

Но это (или мне кажется!) совершенно отличается от случая MaybeT, где m охватывает не внутреннюю часть Maybe, которая есть a, а всю Maybe a.

Если бы StateT должен был напоминать MaybeT (в данном случае использование m для обертывания всего), это было бы так

newtype StateT' s m a = StateT' { runStateT' :: m (s -> (a,s)) }

По этому поводу приходят на ум некоторые мысли:

  • Что в этом плохого?
  • Если бы все было в порядке, разве такой преобразователь монады принес бы мало пользы?
  • Что я вижу в этом неправильного, так это то, что он кажется слишком неограниченным в том смысле, что запуск такого монадного преобразователя поверх IO будет означать, что можно выбрать целое произвольное вычисление с состоянием s -> (a, s) из монады IO, тогда как если фактический StateT накладывается поверх IO, IO можно использовать только для получения нового sсостояния и результата a; но все же я не уверен, что полностью понимаю разницу.

И, наконец, что насчет этого?

newtype StateT'' s m a = StateT'' { runStateT'' :: s -> (m a,s) }

Это кажется немного бесполезным, потому что... такое ощущение, что вычисление с сохранением состояния не имеет смысла, потому что, опять же, в примере с m, являющимся IO, a будет происходить от IO, но... О, я действительно не понимаю.

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

conway.rutgers.edu/~ccshan/wiki/blog/posts/Monad_transformer‌​s может быть интересно. Кажется (в отношении теории категорий), что монадные преобразователи представляют собой специальную коллекцию полезных монадных морфизмов с небольшой базовой структурой, которая объединяет их, например, в том смысле, как человек решает, где m вставляется в тип. (Хотя отмечу, что все общие из них имеют форму f ∘ m ∘ g для функторов f и g, поэтому не кажется совершенно произвольным, куда можно вставить m.)
chepner 12.04.2024 23:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
18
1
483
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы рассмотреть, что может пойти не так, возьмите m = IO и подумайте, какие эффекты могут быть представлены вашими типами, а какие нет.

newtype StateT' s m a = StateT' { runStateT' :: m (s -> (a,s)) }

Ну, это становится IO (s -> (a,s)), поэтому он выполняет эффекты ввода-вывода перед чтением текущего sсостояния для создания нового sсостояния (и результата a).

Таким образом, это не может выразить вычисление «прочитать текущее состояние и распечатать его». Не так уж и полезно.

newtype StateT'' s m a = StateT'' { runStateT'' :: s -> (m a,s) }

Вот и получаем s -> (IO a,s). Из этого легко получить функцию s -> s, которая вычисляет новое состояние в чистом виде. Это означает, что ввод-вывод не может повлиять на новое состояние.

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

Помимо этих проблем, мы должны отметить, что тип, который мы получаем, помещая m в случайные места, может даже не быть монадой! Я не уверен, что мы сможем определить разумный >>= для предложенных вами типов. Попытка сделать это может стать хорошим упражнением, которое может привести к убеждению себя, что это не сработает.

Похоже, что оба эти аппликатива не являются монадами. В первом случае «структура» m-операций фиксирована и на нее не может влиять эволюция состояния. Во втором случае «структура» изменений состояния фиксирована и на нее не могут повлиять m-операции. Как-то аккуратно...

K. A. Buhr 12.04.2024 00:45

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

Что выводит `max`
Могу ли я использовать StateT/MaybeT/forever, чтобы исключить явную рекурсию из этого действия ввода-вывода?
Чтобы сохранить состояние в функции типа a -> ReaderT r IO b, мой единственный вариант - поместить IORef в замыкание? Или я могу как-то использовать StateT?
Как я могу реорганизовать структуру для использования протоколов и замыканий в Swift (цели обучения)
Почему `let fmap f = id >=> (Ok << f)` работает?
Пользовательский экземпляр `Read` завершается с ошибкой, когда тип обернут в Max, но производный экземпляр работает. Что не так с моим экземпляром Read?
В Google Sheets (разрешены только встроенные функции, без скрипта Google Apps). Можно ли имитировать функцию канала?
Есть ли функциональный способ сопоставить список (N элементов) со списком сумм соседних элементов (N-1 элементов) в Котлине?
Как реорганизовать цикл с помощью итератора. (Вернувшись из закрытия)
Настройка значений различных полей классов Java по одному значению для некоторого значения счетчика