好奇为什么进入StateT monad变压器会返回a而不是(a,s)

时间:2019-03-25 08:47:00

标签: haskell

我正在读StateT monad transformer's source,它看起来像这样:

get :: (Monad m) => StateT s m s
get = state $ \ s -> (s, s)

我通过退出state扩展了上面的代码,并得到了它,但是仍然看不到为什么它不返回元组。

a <- StateT (return . (\ s -> (s, s)))

从上面的代码来看,get返回了一个元组(s, s),看起来不错,但是我想知道为什么在使用它时,get返回一个Int,而不是(Int, Int)

我追踪了很多源代码,试图找到何时或什么对其进行了更改,但无济于事。

w :: StateT Int IO String
w = do
  a <- get
  liftIO $ print a -- 2, but why? shouldn't this be (2, 2) instead?
  return $ show a

result = runStateT w 2 -- ("2",2)

1 个答案:

答案 0 :(得分:3)

类型为StateT s m a的包装器,类型为newtype的值是类型为s -> m (a, s)的函数。

函数(return . (\ s -> (s, s)))的类型为s -> m (s, s),因此一旦被StateT构造函数包装后,该函数就成为类型StateT s m s的值。

请注意,类型为StateT s m (s, s)的值将涉及类型为s -> m (s, (s, s))的函数,这不是我们在此处使用的函数。

您的困惑似乎是由s中的“其他” m (s, s)引起的,当我们运行x时,它不会对x <- get有所帮助。要了解原因,考虑一下有状态计算的执行方式很有用:

  • 首先,我们读取类型为s的旧状态。这是s -> ..类型的s -> m (a, s)部分。
  • 然后,我们在monad m中执行一些操作。这是类型.. -> m ..中的s -> m (a, s)部分。
    • 单子动作将返回新状态,以替换旧状态。这是.. -> .. (.., s)类型的s -> m (a, s)部分。
    • 最后,单子动作还返回一个可能不同的类型a的值。类型为.. -> .. (a, ..)的{​​{1}}部分。

运行s -> m (a, s)会自动为我们处理所有这些类型,并让x <- action仅具有结果类型x

具体考虑以下命令性伪代码:

a

在命令式语言中,我们将其键入为global n: int def foo(): if n > 5: print ">5" n = 8 return "hello" else: print "not >5" n = 10 return "greetings" ,因为它返回一个字符串,而不考虑其对全局foo(): string和打印的消息的副作用。

在Haskell中,我们将改用更精确的类型,例如

n: int

再次运行Int -> IO (String, Int) ^-- the old n ^-- the printed stuff ^-- the returned string ^-- the new n ,我们希望x <- foo(),而不是x: string,遵循命令式语言会发生的情况。

如果相反,我们有一个函数

x: (string, int)

我们将使用类型

global n: int

def bar():
   old_n = n
   n = n + 5
   return old_n

因为返回的值现在是Int -> IO (Int, Int) 。同样,

Int

可以使用相同的类型

global n: int

def get():
   return n

现在,有人可能会争辩说这里第二个Int -> IO (Int, Int) 并不是严格必要的,因为Int并没有真正产生新的状态-它不会改变的值get()。尽管如此,使用相同形式的类型n还是很方便的,因为任何可以更改状态的函数。这样便可以与具有类似类型的其他任何功能一起使用。