Я просмотрел https://www.fpcomplete.com/blog/2017/06/tale-of-two-brackets,, хотя просматривал некоторые части, и до сих пор не совсем понимаю основную проблему "StateT
- это плохо, IO
- это нормально", за исключением смутного пониманиячто Haskell позволяет писать плохие StateT
монады (или, как мне кажется, в конечном итоге в статье MonadBaseControl
вместо StateT
).
В пикшах должен соблюдаться следующий закон:
askUnliftIO >>= (\u -> liftIO (unliftIO u m)) = m
Таким образом, это говорит о том, что состояние не изменяется в монаде m
при использовании askUnliftIO
.Но, на мой взгляд, в IO
весь мир может быть государством.Я мог бы, например, читать и писать в текстовый файл на диске.
Цитировать еще одну статью Майкла ,
Ложная чистота Мы говорим WriterT иStateT чисты, и технически они есть.Но давайте будем честными: если у вас есть приложение, которое полностью живет в StateT, вы не получите тех преимуществ сдержанной мутации, которые вам нужны от чистого кода.Можно также назвать вещи своими именами и признать, что у вас есть изменяемая переменная.
Это заставляет меня думать, что это действительно так: с IO мы честны, с StateT
мыне честны в отношении изменчивости ... но это кажется другой проблемой, чем то, что пытается показать закон вышев конце концов, MonadUnliftIO
предполагает IO
.У меня возникают проблемы с концептуальным пониманием того, как IO
является более строгим, чем что-либо еще.
Обновление 1
После сна (некоторые) я все еще смущен, но япостепенно становится все меньше с течением дня.Я разработал законное доказательство для IO
.Я понял наличие id
в README.В частности,
instance MonadUnliftIO IO where
askUnliftIO = return (UnliftIO id)
Таким образом, askUnliftIO
, по-видимому, возвращает IO (IO a)
на UnliftIO m
.
Prelude> fooIO = print 5
Prelude> :t fooIO
fooIO :: IO ()
Prelude> let barIO :: IO(IO ()); barIO = return fooIO
Prelude> :t barIO
barIO :: IO (IO ())
Возвращаясь к закону, он действительно говоритэто состояние не видоизменяется в монаде m
при выполнении туда-обратно преобразованной монады (askUnliftIO
), где в оба конца входит unLiftIO
-> liftIO
.
Возобновление примера выше, barIO :: IO ()
, так что если мы сделаем barIO >>= (u -> liftIO (unliftIO u m))
, то u :: IO ()
и unliftIO u == IO ()
, затем liftIO (IO ()) == IO ()
.** Таким образом, поскольку все в основном были приложения id
под капотом, мы можем видеть, что ни одно состояние не было изменено, даже если мы используем IO
.Важно отметить, что важно то, что значение в a
никогда не запускается и никакое другое состояние не изменяется в результате использования askUnliftIO
.Если это так, то, как и в случае randomIO :: IO a
, мы не сможем получить то же значение, если бы не запустили askUnliftIO
.(Попытка проверки 1 ниже)
Но все равно кажется, что мы могли бы сделать то же самое для других монад, даже если они поддерживают состояние.Но я также вижу, что для некоторых монад мы не можем этого сделать.Думая о надуманном примере: каждый раз, когда мы получаем доступ к значению типа a
, содержащемуся в монаде с состоянием, некоторое внутреннее состояние изменяется.
Попытка проверки 1
> fooIO >> askUnliftIO
5
> fooIOunlift = fooIO >> askUnliftIO
> :t fooIOunlift
fooIOunlift :: IO (UnliftIO IO)
> fooIOunlift
5
Пока хорошо, но не совсем понятно, почему происходит следующее:
> fooIOunlift >>= (\u -> unliftIO u)
<interactive>:50:24: error:
* Couldn't match expected type `IO b'
with actual type `IO a0 -> IO a0'
* Probable cause: `unliftIO' is applied to too few arguments
In the expression: unliftIO u
In the second argument of `(>>=)', namely `(\ u -> unliftIO u)'
In the expression: fooIOunlift >>= (\ u -> unliftIO u)
* Relevant bindings include
it :: IO b (bound at <interactive>:50:1)