因此,我正在编写一个辅助函数来进行广度优先搜索(只是一个爱好项目):
import Control.Monad.State
import qualified Data.Set as S
breadthFirst :: (Monad m, Ord a) => (a -> m [a]) -> [a] -> m ()
breadthFirst f init = evalStateT (go init) S.empty
where
go :: [a] -> StateT (S.Set a) m ()
go [] = return ()
go (x:xs) = do
visited <- gets (S.member x)
if visited then (go xs) else do
modify (S.insert x)
lift (f x) >>= (\n -> go (xs++n))
即将状态从队列中拉出,运行f
以获取更多状态,然后将其放回队列,使用Set
来跟踪已访问状态,以及m
所产生的副作用。 / p>
除非它不会编译:{{1}}等,否则编译器不会认为内部Couldn't match type ‘m’ with ‘m1’
和a
与外部m
和{ {1}},因此认为a
调用不会编译...
但是如果我删除m
的类型断言,我会得到f x
,因为它推断出go
的类型太宽了:
Non-type variable argument in the constraint
我可以用go
来解决这个问题,但是我知道 go :: forall (t :: (* -> *) -> * -> *).
(MonadTrans t, MonadState (S.Set a) (t m)) =>
[a] -> t m ()
的类型,它不是某个任意的FlexibleContexts
实例,而只是go
。如果我将MonadState
替换为StateT
,那么这将为编译器提供所需的额外信息,但这也有点令人反感。
有没有一种方法可以告诉编译器return ()
的类型签名,包括类型变量与外部函数中的类型变量相同?
答案 0 :(得分:6)
是的,它是通过ScopedTypeVariables
扩展名完成的。您需要使用forall
量化外部函数,然后在where子句中定义的函数(除非它们本身具有forall
会引用外部范围)。当您没有作用域类型变量时,每个类型签名都会被隐式量化,因此变量可能与编译器不同。
{-# LANGUAGE ScopedTypeVariables #-}
module SO where
import Control.Monad.State
import qualified Data.Set as S
breadthFirst :: forall m a. (Monad m, Ord a) => (a -> m [a]) -> [a] -> m ()
breadthFirst f init = evalStateT (go init) S.empty
where
go :: [a] -> StateT (S.Set a) m ()
go [] = return ()
go (x:xs) = do
visited <- gets (S.member x)
if visited then (go xs) else do
modify (S.insert x)
lift (f x) >>= (\n -> go (xs++n))