我如何断言内部函数的类型取决于Haskell中外部函数的类型?

时间:2018-10-12 19:59:33

标签: haskell

因此,我正在编写一个辅助函数来进行广度优先搜索(只是一个爱好项目):

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 ()的类型签名,包括类型变量与外部函数中的类型变量相同?

1 个答案:

答案 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))