临时类型变量的作用域限定

5

我有大量现成的向量函数,类型如下:

f :: (M.MVector v r, PrimMonad m) => 
     v (PrimState m) r -> v (PrimState m) r -> m ()

这些函数大多数是原地操作的,因此将其参数设置为可变向量非常方便,这样我就可以进行组合、迭代等操作。但是,在顶层,我只想使用不可变的“Haskell”/纯向量来操作。

以下是一个示例:

{-# LANGUAGE TypeFamilies, 
             ScopedTypeVariables, 
             MultiParamTypeClasses, 
             FlexibleInstances #-}

import Data.Vector.Generic as V hiding (eq)
import Data.Vector.Generic.Mutable as M
import Control.Monad.ST
import Control.Monad.Primitive

f :: (M.MVector v r, PrimMonad m) => 
     v (PrimState m) r -> v (PrimState m) r -> m ()
f vIn vOut = do val <- M.read vIn 0
                M.write vOut 0 val

applyFunc :: (M.MVector v r, PrimMonad m, V.Vector v' r, v ~ Mutable v') => 
             (v (PrimState m) r -> v (PrimState m) r -> m ()) -> v' r -> v' r
applyFunc g x = runST $ do
                    y <- V.thaw x
                    g y y -- LINE 1
                    V.unsafeFreeze y

topLevelFun :: (V.Vector v r) => r -> v r
topLevelFun a =
    let x = V.replicate 10 a
    in applyFunc f x -- LINE 2

代码如此编写,会在第1行产生错误:
Could not deduce (m ~ ST s)
   Expected type: ST s ()
   Actual type: m ()
   in the return type of g, LINE 1

注释掉第1行会导致第2行出错:

Ambiguous type variable `m0' in the constraint:
    (PrimMonad m0) arising from a use of `applyFun'

我尝试过多种明确的类型(使用ScopedTypeVariables、显式的forall等),但没有找到修复第一个错误的方法。对于LINE 1的错误,似乎应该将m推断为ST s,因为我在runST中。

对于LINE 2的错误(LINE 1已注释掉),我唯一想到的解决办法是:

class Fake m v where
    kindSig :: m a -> v b c

instance Fake m v

topLevelFun :: forall m v v' r . (V.Vector v' r, M.MVector v r, PrimMonad m, Fake m v, v ~ Mutable v') => r -> v' r
topLevelFun a =
    let x = V.replicate 10 a
    in applyFunc (f::Transform m v r)  x -- LINE 2

这显然是不令人满意的:我必须创建一个假类,具有更加无意义的方法,其唯一的工作是展示类参数的类型。然后我为所有内容创建一个通用实例,以便我可以在topLevelFun中使用m,从而可以添加约束并强制转换f。肯定有更好的方法。
我可能在这里做了各种错误的事情,因此任何建议都将是有帮助的。

applyFunc 承诺适用于调用者选择的任何 PrimMonad m,但是 runST $ do ... 强制 mST s。你可以通过声明 g 适用于你选择的任何 PrimMonad m,或者仅限制 mST s 来解决这个问题。这是代码。如果这确实回答了你的问题,请让我知道,我会写下一个答案。 - Vitus
我相信这个可以工作,只需要进行一些小的更改。请确保在语言导入中添加“Rank2Types”,并且applyFunc中的rank2类型应该是(forall m. (PrimMonad m) => ...)。非常感谢!最终成功引入了Rank2Types。 - crockeea
啊,是的,我忘记了我在.ghci中有RankNTypes。顺便说一下,我相信Rank2Types正在被弃用,取而代之的是RankNTypes - Vitus
谢谢你的提示。我有一个小变体要问。显然,rank 2 forall'd函数是否在pair中很重要。applyFunc的实际类型是:(M.MVector v r, V.Vector v' r, v ~ Mutable v') => (forall m . (PrimMonad m) => ((v (PrimState m) r -> v (PrimState m) r -> m ()), Int)) -> v' r -> v' r。但是,仅仅添加这个pair(以跟踪大小)会导致新的错误couldn't match type m0 with 'ST s'。为什么添加pair会导致这种情况?是GHC的问题吗? - crockeea
我真的需要添加“ImpredicativeTypes”扩展名才能将等级2量化器移动到对中吗?对于这样一个温和的变化来说,这似乎相当极端。applyFunc的新类型是:(M.MVector v r, V.Vector v' r, v ~ Mutable v') => ((forall m . (PrimMonad m) => ((v (PrimState m) r -> v (PrimState m) r -> m ()), Int) -> v' r -> v' r。使用ImpredicativeTypes,这可以编译。此外,如果这是一个合理的解决方案,则不再需要Rank2 / NTypes。 - crockeea
ImpredicativeTypes 意味着 RankNTypes,所以你无论如何都需要这个扩展。但是,如果你想在某些结构中保留多态函数,你需要使用特定的数据类型(例如 newtype I = I (forall a. a -> a))或者 ImpredicativeTypes - Vitus
1个回答

1

以下的applyFunc类型对您有效吗?

applyFunc :: (Vector v a) => 
  (forall s. Mutable v s a -> Mutable v s a -> ST s ()) 
  -> v a -> v a

只要您拥有Rank2Types扩展,那么这应该可以编译通过,因为您使用的函数必须在所有 ST monad上工作。原因是runST的类型是(forall s. ST s a) -> a,因此在runST之后的代码体需要适用于所有的s,因此g需要适用于所有的s

(您也可以采用适用于所有PrimMonads的函数,但这些函数数量严格较少)。

GHC无法推断高阶类型。不推断RankNTypes有非常好的理由(它是不可判定的),虽然Rank2在理论上是可推断的,但GHC的开发人员决定遵循“仅在原则类型是Hindley-Milner类型时才进行推断”的规则,对像我这样的人来说非常容易推理,并使得编译器编写者的工作不那么困难。

在评论中,您询问如何获取元组。具有多态类型的元组需要使用ImpredicativeTypes,可以像这样完成
applyFuncInt :: (Vector v a) => 
   ((forall s. Mutable v s a -> Mutable v s a -> ST s ()),Int)
   -> v a -> v a
applyFuncInt (g,_) x = runST $ do
                            y <- V.thaw x
                            g y y
                            V.unsafeFreeze y

虽然通常最好将数字作为单独的参数传递。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接