我正在读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)
答案 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)
部分。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
还是很方便的,因为任何可以更改状态的函数。这样便可以与具有类似类型的其他任何功能一起使用。