当我引入一个使用 Monad 作为参数的新参数时,为什么函数之间有效的 monadic 链接会中断?
Why does a working monadic linkage between functions break, when I introduce a new argument that uses the Monad as a parameter?
以下代码:
-- tst2.hs - showing successful monadic linkage.
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}
import Protolude
import Control.Monad.Extra (unfoldM)
foo :: Monad m
=> Int
-> m [[Int]]
foo n = evalStateT (traverse nxt [1..n]) 0
where nxt _ = do s <- get
r <- bar s
put $ s + 1
return r
bar :: Monad m
=> Int
-> m [Int]
bar n = unfoldM step n
where step k = return $ if k > 0 then Just (k, k - 1)
else Nothing
main :: IO ()
main = do xs <- foo 3
print xs
工作正常,生成此输出:
Davids-Air-2:haskell-rl dbanas$ stack runghc tst2.hs
[[],[1],[2,1]]
但是,如果我稍微更改一下代码:
-- tst3.hs - showing breakage of monadic linkage.
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}
import Protolude
import Control.Monad.Extra (unfoldM)
data Dummy m = Dummy (Int -> m Int)
foo :: Monad m
=> Int
-> Dummy m
-> m [[Int]]
foo n d = evalStateT (traverse nxt [1..n]) 0
where nxt _ = do s <- get
r <- bar s d
put $ s + 1
return r
bar :: Monad m
=> Int
-> Dummy m
-> m [Int]
bar n d = unfoldM step n
where step k = return $ if k > 0 then Just (k, k - 1)
else Nothing
main :: IO ()
main = do xs <- foo 3 $ Dummy (\n -> return n)
print xs
我收到以下编译器错误:
Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs
tst3.hs:15:23: error:
• Couldn't match type ‘m’ with ‘StateT Integer m’
‘m’ is a rigid type variable bound by
the type signature for:
foo :: forall (m :: * -> *). Monad m => Int -> Dummy m -> m [[Int]]
at tst3.hs:(11,1)-(14,16)
Expected type: StateT Integer m [[Int]]
Actual type: m [[Int]]
• In the first argument of ‘evalStateT’, namely
‘(traverse nxt [1 .. n])’
In the expression: evalStateT (traverse nxt [1 .. n]) 0
In an equation for ‘foo’:
foo n d
= evalStateT (traverse nxt [1 .. n]) 0
where
nxt _
= do s <- get
....
• Relevant bindings include
nxt :: forall p. p -> m [Int] (bound at tst3.hs:16:8)
d :: Dummy m (bound at tst3.hs:15:7)
foo :: Int -> Dummy m -> m [[Int]] (bound at tst3.hs:15:1)
|
15 | foo n d = evalStateT (traverse nxt [1..n]) 0
| ^^^^^^^^^^^^^^^^^^^
结果证明解决方案非常简单,虽然我花了几个小时来推导:
r <- lift $ bar s d
Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs
[[],[1],[2,1]]
我对此有一个理论并希望确认:
在bar
中运行的Monad与在foo
中运行的Monad不同。
具体来说,在 foo
中运行的 Monad 是在 bar
中运行的 Monad 的 lifted 版本。 (准确地说,提升到 StateT
上下文中。)
在 tst2.hs
的情况下,两个 m
之间没有类型链接(foo
和 bar
),这非常有效。
但是,在 tst3.hs
中,我提供了两者之间的类型链接,迫使它们成为同一个 Monad。
这就是编译器抱怨的原因。
我的解决方案有效,只是因为在 foo
中运行的 Monad 确实是在 bar
中运行的 lifted 版本。
如果这两个 Monad 完全不相关,那么我的解决方案 不会 有效。
都是这样吗?
当您从 main
调用 foo
时,您是说 Monad m
应该是 IO
。因此,您从 Dummy (\n -> return n)
得到一个 Dummy IO
(顺便说一下,这可能只是 Dummy return
)。然后在 foo
中,您使用 Dummy IO
参数调用 bar
,因此在 bar
的 Monad m
中将 m
设置为 IO
].但是,对 bar
的调用是在 StateT Int IO
内而不是 IO
内,因此出现错误。
如您所见,您可以使用 Dummy IO
调用 bar,然后将结果提升到 StateT Int IO
。您也正确地观察到在某些情况下这是行不通的。
您可能需要考虑另一种解决方案。如果你实际上并不关心你的 Dummy
类型需要什么 monad(似乎是这种情况),你可以强制它在所有 monads 中工作:
newtype Dummy = Dummy (Int -> (forall m. Monad m => m Int))
这需要 RankNTypes
扩展。
以下代码:
-- tst2.hs - showing successful monadic linkage.
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}
import Protolude
import Control.Monad.Extra (unfoldM)
foo :: Monad m
=> Int
-> m [[Int]]
foo n = evalStateT (traverse nxt [1..n]) 0
where nxt _ = do s <- get
r <- bar s
put $ s + 1
return r
bar :: Monad m
=> Int
-> m [Int]
bar n = unfoldM step n
where step k = return $ if k > 0 then Just (k, k - 1)
else Nothing
main :: IO ()
main = do xs <- foo 3
print xs
工作正常,生成此输出:
Davids-Air-2:haskell-rl dbanas$ stack runghc tst2.hs
[[],[1],[2,1]]
但是,如果我稍微更改一下代码:
-- tst3.hs - showing breakage of monadic linkage.
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts #-}
import Protolude
import Control.Monad.Extra (unfoldM)
data Dummy m = Dummy (Int -> m Int)
foo :: Monad m
=> Int
-> Dummy m
-> m [[Int]]
foo n d = evalStateT (traverse nxt [1..n]) 0
where nxt _ = do s <- get
r <- bar s d
put $ s + 1
return r
bar :: Monad m
=> Int
-> Dummy m
-> m [Int]
bar n d = unfoldM step n
where step k = return $ if k > 0 then Just (k, k - 1)
else Nothing
main :: IO ()
main = do xs <- foo 3 $ Dummy (\n -> return n)
print xs
我收到以下编译器错误:
Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs
tst3.hs:15:23: error:
• Couldn't match type ‘m’ with ‘StateT Integer m’
‘m’ is a rigid type variable bound by
the type signature for:
foo :: forall (m :: * -> *). Monad m => Int -> Dummy m -> m [[Int]]
at tst3.hs:(11,1)-(14,16)
Expected type: StateT Integer m [[Int]]
Actual type: m [[Int]]
• In the first argument of ‘evalStateT’, namely
‘(traverse nxt [1 .. n])’
In the expression: evalStateT (traverse nxt [1 .. n]) 0
In an equation for ‘foo’:
foo n d
= evalStateT (traverse nxt [1 .. n]) 0
where
nxt _
= do s <- get
....
• Relevant bindings include
nxt :: forall p. p -> m [Int] (bound at tst3.hs:16:8)
d :: Dummy m (bound at tst3.hs:15:7)
foo :: Int -> Dummy m -> m [[Int]] (bound at tst3.hs:15:1)
|
15 | foo n d = evalStateT (traverse nxt [1..n]) 0
| ^^^^^^^^^^^^^^^^^^^
结果证明解决方案非常简单,虽然我花了几个小时来推导:
r <- lift $ bar s d
Davids-Air-2:haskell-rl dbanas$ stack runghc tst3.hs
[[],[1],[2,1]]
我对此有一个理论并希望确认:
在bar
中运行的Monad与在foo
中运行的Monad不同。
具体来说,在 foo
中运行的 Monad 是在 bar
中运行的 Monad 的 lifted 版本。 (准确地说,提升到 StateT
上下文中。)
在 tst2.hs
的情况下,两个 m
之间没有类型链接(foo
和 bar
),这非常有效。
但是,在 tst3.hs
中,我提供了两者之间的类型链接,迫使它们成为同一个 Monad。
这就是编译器抱怨的原因。
我的解决方案有效,只是因为在 foo
中运行的 Monad 确实是在 bar
中运行的 lifted 版本。
如果这两个 Monad 完全不相关,那么我的解决方案 不会 有效。
都是这样吗?
当您从 main
调用 foo
时,您是说 Monad m
应该是 IO
。因此,您从 Dummy (\n -> return n)
得到一个 Dummy IO
(顺便说一下,这可能只是 Dummy return
)。然后在 foo
中,您使用 Dummy IO
参数调用 bar
,因此在 bar
的 Monad m
中将 m
设置为 IO
].但是,对 bar
的调用是在 StateT Int IO
内而不是 IO
内,因此出现错误。
如您所见,您可以使用 Dummy IO
调用 bar,然后将结果提升到 StateT Int IO
。您也正确地观察到在某些情况下这是行不通的。
您可能需要考虑另一种解决方案。如果你实际上并不关心你的 Dummy
类型需要什么 monad(似乎是这种情况),你可以强制它在所有 monads 中工作:
newtype Dummy = Dummy (Int -> (forall m. Monad m => m Int))
这需要 RankNTypes
扩展。