秩-2类型的模式匹配

时间:2014-07-14 19:27:41

标签: haskell ghc

我试图理解为什么这个代码的一个版本编译,而一个版本不编译。

{-# LANGUAGE RankNTypes, FlexibleContexts #-}

module Foo where

import Data.Vector.Generic.Mutable as M
import Data.Vector.Generic as V
import Control.Monad.ST
import Control.Monad.Primitive

data DimFun v m r = 
  DimFun {dim::Int, func :: v (PrimState m) r -> m ()}

runFun1 :: (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun1 (DimFun dim t) x | V.length x == dim = runST $ do
  y <- thaw x
  t y
  unsafeFreeze y

runFun2 :: (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun2 t x = runST $ do
  y <- thaw x
  evalFun t y
  unsafeFreeze y

evalFun :: (PrimMonad m, MVector v r) => DimFun v m r -> v (PrimState m) r -> m ()
evalFun (DimFun dim f) y | dim == M.length y = f y

runFun2编译正常(GHC-7.8.2),但runFun1会导致错误:

Could not deduce (PrimMonad m0) arising from a pattern
from the context (Vector v r, MVector (Mutable v) r)
  bound by the type signature for
             tfb :: (Vector v r, MVector (Mutable v) r) =>
                    (forall (m :: * -> *). PrimMonad m => TensorFunc m r) -> v r -> v r
  at Testing/Foo.hs:(26,8)-(28,15)
The type variable ‘m0’ is ambiguous
Note: there are several potential instances:
  instance PrimMonad IO -- Defined in ‘Control.Monad.Primitive’
  instance PrimMonad (ST s) -- Defined in ‘Control.Monad.Primitive’
In the pattern: TensorFunc _ f
In an equation for ‘tfb’:
    tfb (TensorFunc _ f) x
      = runST
        $ do { y <- thaw x;
               f y;
               unsafeFreeze y }

Couldn't match type ‘m0’ with ‘ST s’
  because type variable ‘s’ would escape its scope
This (rigid, skolem) type variable is bound by
  a type expected by the context: ST s (v r)
  at Testing/Foo.hs:(29,26)-(32,18)
Expected type: ST s ()
  Actual type: m0 ()
Relevant bindings include
  y :: Mutable v s r (bound at Testing/Foo.hs:30:3)
  f :: forall (v :: * -> * -> *).
       MVector v r =>
       v (PrimState m0) r -> m0 ()
    (bound at Testing/Foo.hs:29:19)
In a stmt of a 'do' block: f y
In the second argument of ‘($)’, namely
  ‘do { y <- thaw x;
        f y;
        unsafeFreeze y }’

Could not deduce (s ~ PrimState m0)
from the context (Vector v r, MVector (Mutable v) r)
  bound by the type signature for
             tfb :: (Vector v r, MVector (Mutable v) r) =>
                    (forall (m :: * -> *). PrimMonad m => TensorFunc m r) -> v r -> v r
  at Testing/Foo.hs:(26,8)-(28,15)
  ‘s’ is a rigid type variable bound by
      a type expected by the context: ST s (v r) at Testing/Foo.hs:29:26
Expected type: Mutable v (PrimState m0) r
  Actual type: Mutable v s r
Relevant bindings include
  y :: Mutable v s r (bound at Testing/Foo.hs:30:3)
  f :: forall (v :: * -> * -> *).
       MVector v r =>
       v (PrimState m0) r -> m0 ()
    (bound at Testing/Foo.hs:29:19)
In the first argument of ‘f’, namely ‘y’
In a stmt of a 'do' block: f y

我很确定排名2类型是可以归咎的,可能是由单态限制引起的。但是,正如我previous question中所建议的那样,我启用了-XNoMonomorphismRestriction,但却遇到了同样的错误。

这些看似相同的代码片段之间有什么区别?

3 个答案:

答案 0 :(得分:12)

我认为在这里涉及类型级管道的粗略心理模型是必不可少的,所以我要去谈谈&#34;隐含的事情&#34;更详细一点,并在此之后仔细检查你的问题。只对该问题的直接解决方案感兴趣的读者可以跳过关于多态值的模式匹配&#34;小节和结束。

1。隐式函数参数

输入参数

GHC将Haskell编译成一种名为Core的小型中间语言,它本质上是一种名为System F的Rank-n多态类型lambda演算(加上一些扩展)。下面我将使用Haskell和一个有点像Core的符号;我希望它不会过于混乱。

在Core中,多态函数是将类型作为附加参数的函数,而在该行下面的参数可以引用那些类型或具有这些类型:

-- in Haskell
const :: forall (a :: *) (b :: *). a -> b -> a
const x y = x

-- in pseudo-Core
const' :: (a :: *) -> (b :: *) -> a -> b -> a
const' a b x y = x 

这意味着每当我们想要使用它们时,我们还必须为这些函数提供类型参数。在Haskell中,类型推断通常会计算出类型参数并自动提供它们,但如果我们查看Core输出(例如,请参阅此introduction了解如何执行此操作),则类型参数和应用程序随处可见。建立一个这样的心智模型可以更容易地计算出更高级别的代码:

-- Haskell
poly :: (forall a. a -> a) -> b -> (Int, b)
poly f x = (f 0, f x)

-- pseudo-Core
poly' :: (b :: *) -> ((a :: *) -> a -> a) -> b -> (Int, b)
poly' b f x = (f Int 0, f b x)

它清楚地说明了为什么有些事情没有进行类型检查:

wrong :: (a -> a) -> (Int, Bool)
wrong f = (f 0, f True)

wrong' :: (a :: *) -> (a -> a) -> (Int, Bool)
wrong' a f = (f ?, f ?) -- f takes an "a", not Int or Bool. 

类约束参数

-- Haskell
show :: forall a. Show a => a -> String
show x = show x

-- pseudo-Core
show' :: (a :: *) -> Show a -> a -> String
show' a (ShowDict showa) x = showa x 

这里的ShowDictShow a是什么? ShowDict只是一个包含show实例的Haskell记录,GHC为每个类的实例生成这样的记录。 Show a只是此实例记录的类型:

-- We translate classes to a record type:
class Show a where show :: a -> string

data Show a = ShowDict (show :: a -> String)

-- And translate instances to concrete records of the class type:
instance Show () where show () = "()"

showUnit :: Show ()
showUnit = ShowDict (\() -> "()")

例如,每当我们想要应用show时,编译器必须搜索范围以便为该类型找到合适的类型参数和实例字典。请注意,虽然实例始终是顶级的,但在多态函数中,实例作为参数传递:

data Foo = Foo

-- instance Show Foo where show _ = "Foo"
showFoo :: Show Foo
showFoo = ShowDict (\_ -> "Foo")

-- The compiler fills in an instance from top level
fooStr :: String
fooStr = show' Foo showFoo Foo 

polyShow :: (Show a, Show b) => a -> b -> String
polyShow a b = show a ++ show b

-- Here we get the instances as arguments (also, note how (++) also takes an extra
-- type argument, since (++) :: forall a. [a] -> [a] -> [a])
polyShow' :: (a :: *) -> (b :: *) -> Show a -> Show b -> a -> b -> String
polyShow' a b (ShowDict showa) (ShowDict showb) a b -> (++) Char (showa a) (showb b) 

多态值的模式匹配

在Haskell中,函数的模式匹配没有意义。多态值也可以被视为函数,但我们可以对它们进行模式匹配,就像在OP的错误runfun1示例中一样。但是,所有隐式参数都必须在范围内可推断,否则模式匹配的单纯行为就是类型错误:

import Data.Monoid

-- it's a type error even if we don't use "a" or "n".
-- foo :: (forall a. Monoid a => (a, Int)) -> Int
-- foo (a, n) = 0 

foo :: ((a :: *) -> Monoid a -> (a, Int)) -> Int
foo f = ? -- What are we going to apply f to?

换句话说,通过对多态值进行模式匹配,我们断言已经应用了所有隐式参数。在这里foo的情况下,尽管Haskell中没有类型应用程序的语法,但我们可以使用类型注释:

{-# LANGUAGE ScopedTypeVariables, RankNTypes #-}

foo :: (forall a. Monoid a => (a, Int)) -> Int
foo x = case (x :: (String, Int)) of (_, n) -> n

-- or alternatively
foo ((_ :: String), n) = n 

再次,伪核心使情况更加清晰:

foo :: ((a :: *) -> Monoid a -> (a, Int)) -> Int
foo f = case f String monoidString of (_ , n) -> n 

此处monoidStringMonoid的一些可用String实例。

2。隐式数据字段

隐式数据字段通常对应于&#34;存在类型&#34;的概念。在哈斯克尔。从某种意义上说,它们是关于期限义务的隐含函数论证的双重性:

  • 当我们构造函数时,隐式参数在函数体中可用。
  • 当我们申请职能时,我们有额外的义务来履行。
  • 当我们用隐式字段构建数据时,我们必须提供那些额外的字段。
  • 当我们模式匹配数据时,隐式字段也会进入范围。

标准示例:

{-# LANGUAGE GADTs #-}

data Showy where
    Showy :: forall a. Show a => a -> Showy

-- pseudo-Core
data Showy where
    Showy :: (a :: *) -> Show a -> a -> Showy

-- when constructing "Showy", "Show a" must be also available:
someShowy :: Showy
someShowy = Showy (300 :: Int)

-- in pseudo-Core
someShowy' = Showy Int showInt 300 

-- When pattern matching on "Showy", we get an instance in scope too
showShowy :: Showy -> String
showShowy (Showy x) = show x 

showShowy' :: Showy -> String
showShowy' (Showy a showa x) = showa x

3。看一下OP的例子

我们有功能

runFun1 :: (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun1 dfun@(DimFun dim t) x | V.length x == dim = runST $ do
    y <- thaw x
    t y
    unsafeFreeze y

请记住,对多态值的模式匹配断言所有隐式参数在范围内都可用。除此之外,在模式匹配时,范围内根本没有m,更不用说PrimMonad实例了。

使用GHC 7.8.x,优先使用type holes是很好的做法:

runFun1 :: (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun1 (DimFun dim t) x | V.length x == dim = _

现在GHC将正确显示孔的类型,以及上下文中变量的类型。我们可以看到t的类型为Mutable v (PrimState m0) r -> m0 (),我们也看到m0未列在任何地方。事实上,这是一个臭名昭着的&#34;模棱两可的&#34;由GHC构成的类型变量作为占位符。

那么,为什么我们不尝试手动提供参数,就像前面的Monoid实例一样?我们知道我们会在t操作中使用ST,因此我们可以尝试将m修改为ST s,GHC会自动为我们应用PrimMonad个实例:

runFun1 :: forall v r. (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun1 (DimFun dim (t :: Mutable v s r -> ST s ())) x 
    | V.length x == dim = runST $ do 
        y <- thaw x
        t y
        unsafeFreeze y

...除非它不起作用,否则我们会收到错误"Couldn't match type ‘s’ with ‘s1’ because type variable ‘s1’ would escape its scope"

事实证明 - 毫不奇怪 - 我们已经忘记了另一个隐含的论点。回想一下runST

的类型
runST :: (forall s. ST s a) -> a

我们可以想象runST采用((s :: PrimState ST) -> ST s a)类型的函数,然后我们的代码如下所示:

runST $ \s -> do
    y <- thaw x   -- y :: Mutable v s r
    t y           -- error: "t" takes a "Mutable v s r" with a different "s". 
    unsafeFreeze y 

s参数类型中的t在最外层范围内默默引入:

runFun1 :: forall v s r. ...

因此两个s - es是截然不同的。

一种可能的解决方案是在ST动作内的DimFun参数上进行模式匹配。在那里,正确的s在范围内,GHC可以ST s提供m

runFun1 :: forall v r. (Vector v r, MVector (Mutable v) r) => 
    (forall m . PrimMonad m => DimFun (Mutable v) m r) -> v r -> v r
runFun1 dimfun x = runST $ do
    y <- thaw x
    case dimfun of
        DimFun dim t | dim == M.length y -> t y
    unsafeFreeze y

明确指出一些参数:

runST $ \s -> do
    y <- thaw x
    case dimfun (ST s) primMonadST of
         DimFun dim t | dim == M.length y -> t y
    unsafeFreeze y 

作为一项练习,让我们将所有函数转换为伪核心(但是不要让do语法消失,因为那太难看了):

-- the full types of the functions involved, for reference
thaw :: forall m v a. (PrimMonad m, V.Vector v a) => v a -> m (V.Mutable v (PrimState m) a)
runST :: forall a. (forall s. ST s a) -> a
unsafeFreeze :: forall m v a. (PrimMonad m, Vector v a) => Mutable v (PrimState m) a -> v a 
M.length :: forall v s a. MVector v s a -> Int
(==) :: forall a. Eq a => a -> a -> Bool

runFun1 :: 
    (v :: * -> *) -> (r :: *) 
    -> Vector v r -> MVector (Mutable v) r
    -> ((m :: (* -> *)) -> PrimMonad m -> DimFun (Mutable v) m r)
    -> v r -> v r
runFun1 v r vecInstance mvecInstance dimfun x = runST r $ \s -> do
    y <- thaw (ST s) v r primMonadST vecInstance x
    case dimFun (ST s) primMonadST of
        DimFun dim t | (==) Int eqInt dim (M.length v s r y) -> t y
    unsafeFreeze (ST s) v r primMonadST vecInstance y

那是满口的。

现在我们有能力解释为什么runFun2有效:

runFun2 :: (Vector v r, MVector (Mutable v) r) => 
  (forall m . (PrimMonad m) => DimFun (Mutable v) m r) -> v r -> v r
runFun2 t x = runST $ do
  y <- thaw x
  evalFun t y
  unsafeFreeze y

evalFun :: (PrimMonad m, MVector v r) => DimFun v m r -> v (PrimState m) r -> m ()
evalFun (DimFun dim f) y | dim == M.length y = f y

evalFun只是一个多态函数,可以在正确的位置调用(我们最终在正确的位置t上进行模式匹配),其中正确的ST s可用作{ {1}}论点。

随着类型系统越来越复杂,模式匹配变得越来越严重,具有深远的影响和非平凡的要求。在谱图的最后,您可以找到完全依赖的语言和证明助手,例如Agda,Idris或Coq,其中对一个数据的模式匹配可能意味着在程序的某个分支中接受任意逻辑命题为true。

答案 1 :(得分:4)

虽然@AndrasKovacs给出了一个很好的答案,但我认为值得指出如何完全避免这种肮脏。我This answer对相关问题展示了DimFun的“正确”定义如何使所有排名第2的内容消失。

DimFun定义为

data DimFun v r = 
  DimFun {dim::Int, func :: forall s . (PrimMonad s) => v (PrimState s) r -> s ()}

runFun1变为:

runFun1 :: (Vector v r)
        => DimFun (Mutable v) r -> v r -> v r
runFun1 (DimFun dim t) x | dim == V.length x = runST $ do
  y <- thaw x
  t y
  unsafeFreeze y

并编译没有问题。

答案 2 :(得分:1)

我认为不允许对约束值进行模式匹配。特别是,您可以使用模式匹配,但仅适用于修复约束中的类型并选择特定实例的GADT构造函数。否则,我得到模糊的类型变量错误。

也就是说,我不认为GHC可以统一匹配模式(DimFun dim t)的值类型(forall m . (PrimMonad m) => DimFun (Mutable v) m r)

请注意,evalFun中的模式匹配看起来相似,但允许在m上设置约束,因为量化的范围是整个evalFun;相反,runFun1作为量化m的较小范围。

HTH