具有种类的部分应用类型(* - >约束)

时间:2014-07-25 22:28:23

标签: haskell ghc

请考虑以下代码:

class Foo f
class Bar b

newtype D d = D

call :: Proxy c -> (forall a . c a => a -> Bool) -> x -> Bool
call g x = g x

-- this function is Testable, and can be used by QuickCheck
f :: (Foo (D d), Bar d) => D d -> Bool
f = error ""

//assume the type FConstraint has kind (* -> Constraint)

main = print $ call (Proxy::Proxy FConstraint) (unsafeCoerce f) (D::D 17)

这样一个奇怪的函数的意图是能够使用QuickCheck轻松测试多态函数(例如f):call将遍历单态类型的类型列表,并且此列表可以用于几个不同的属性(都采用单个参数),而不是每次都明确写出类型签名。因为这只出现在测试代码中,所以我愿意使用unsafeCoerce(虽然我不确定它是否能满足我的需求。我需要首先解决下面的问题。)

我肯定对call的更好/替代设计持开放态度,但这里是关于我上面提出的解决方案的问题:我需要一个FConstraint类型(* -> Constraint)可用于call的第一个参数。

这是我尝试过的:

type FConstraint x = (Foo x, Bar ??)
type FConstraint (D d) = (Foo (D d), Bar d)

两种类型的同义词都不起作用,因为我需要模式匹配以获得所有约束,并且还需要部分应用 FConstraint,同义词不允许使用哪种类型。

我也尝试了一个类型系列:

type family FConstraint x
type instance FConstraint (D d) = (Foo (D d), Bar d)

但我仍然不能部分应用这个(我也不能使用封闭式家庭)。

因此我需要一种类型(* -> Constraint)

  • 模式匹配类型*
  • 可以部分应用

更新

以下是使用以下答案中的想法的几乎编译示例:

{-# LANGUAGE RankNTypes, ScopedTypeVariables, ConstraintKinds, TypeOperators,
             MultiParamTypeClasses, DataKinds, PolyKinds, FlexibleInstances,
             UndecidableInstances, FlexibleContexts, TypeFamilies,
             FunctionalDependencies #-}

import Control.Monad
import Data.Proxy
import GHC.Prim
import GHC.TypeLits
import Test.QuickCheck
import Test.Framework
import Test.Framework.Providers.QuickCheck2
import Unsafe.Coerce

newtype Zq q = Zq Integer deriving (Show, Eq)

instance (KnownNat q) => Arbitrary (Zq q) where
  arbitrary = liftM (\i -> Zq $ (i `mod` (natVal (Proxy::Proxy q)))) arbitrary

instance (KnownNat q) => Num (Zq q) where
  (Zq a) + (Zq b) = Zq $ (a + b) `mod` (natVal (Proxy::Proxy q))
  (Zq a) * (Zq b) = Zq $ (a * b) `mod` (natVal (Proxy::Proxy q))
  negate (Zq 0) = (Zq 0)
  negate (Zq a) = Zq $ (natVal (Proxy::Proxy q)) - a
  fromInteger x = Zq $ x `mod` (natVal (Proxy::Proxy q))

f :: (KnownNat q) => Zq q -> Zq q
f x = x + 1

finv :: (KnownNat q) => Zq q -> Zq q
finv x = x - 1

prop_f :: forall q . (KnownNat q) => Zq q -> Bool
prop_f x = (finv . f) x == x

call :: (c x) => Proxy (c :: * -> Constraint) -> 
        (forall a . c a => a -> Bool) -> 
        x -> 
        Bool
call _ f = f

class UnZq zq q | zq -> q
instance UnZq (Zq q) q

class FConstraint x
instance (KnownNat q, UnZq zq q) => FConstraint zq

main = defaultMain $ [testProperty "zq_f_id" $ property $
         (call (Proxy::Proxy FConstraint) $ unsafeCoerce prop_f :: Zq 3 -> Bool)]

我收到错误:

Could not deduce (KnownNat q0) arising from a use of ‘prop_f’
from the context (FConstraint a)

我希望这是一个简单的范围界定问题......

以下是用例的更多代码:

type MyTypes = '[ Zq 3, Zq 5, Zq 7, Zq 11 ] 

class TypesToProps xs c where
  tmap :: Proxy xs -> Proxy c -> (forall a . c a => a -> Bool) -> [Property]

instance TypesToProps '[] c where
  tmap _ _ _ = []

instance (c x, TypesToProps xs c, Arbitrary x, Show x)
    => TypesToProps (x ': xs) c where
  tmap _ c f = (property (call c f :: x -> Bool)) : (tmap (Proxy::Proxy xs) c f)

-- results in same error as the `main` above
main = defaultMain $ map (testProperty "zq_f_id") $ 
         tmap (Proxy::Proxy MyTypes) 
              (Proxy::Proxy FConstraint) 
              (unsafeCoerce prop_f)

1 个答案:

答案 0 :(得分:3)

首先,您的call函数无法编译,也不应该编译:无法推断类型c x中的Proxy c -> (forall a . c a => a -> Int) -> x -> Int,因此您无法通过xf

接下来,您在unsafeCoerce的使用无效。如果您查看使用它的unsafeCoerce类型:

withCall 
  :: (forall d . (Foo (D d), Bar d) => D d -> Bool) 
  -> (forall a . FConstraint a => a -> Int)
withCall = unsafeCoerce

您将D d投射到任何类型Bool投放到Int。 (注意这需要ImpredictiveTypes)。我不了解你的用例,所以我不能说是否有比unsafeCoerce更好的解决方案,但我可以说你正在做的事情可能会在你的脸上爆炸。

写实际的FConstraint非常简单:

class UnD x (y :: Nat) | x -> y 
instance UnD (D x) x 

class FConstraint x
instance (Foo x, Bar t, UnD x t) => FConstraint x

完整的代码:

{-# LANGUAGE 
    UndecidableInstances
  , ImpredicativeTypes
  , MultiParamTypeClasses
  , FunctionalDependencies 
  #-}

import Unsafe.Coerce 
import GHC.Exts (Constraint)
import Data.Proxy 
import GHC.TypeLits 

class Foo f
class Bar (b :: Nat)

data D (d :: Nat) = D

call :: Proxy (c :: * -> Constraint) -> (forall a . c a => a -> Int) -> x -> Int
call = undefined

f :: (Foo (D d), Bar d) => D d -> Bool
f = error ""

withCall :: (forall d . (Foo (D d), Bar d) => D d -> Bool) -> (forall a . FConstraint a => a -> Int)
withCall = unsafeCoerce

class UnD x (y :: Nat) | x -> y 
instance UnD (D x) x 

class FConstraint x
instance (Foo x, Bar t, UnD x t) => FConstraint x

main = print $ call (Proxy :: Proxy FConstraint) (withCall f) (D :: D 17)

为了回应您的更新,显而易见的事情就是重构您的TypesToProps课程:

class TypesToProps xs (c :: k -> Constraint) (t :: k -> *) where
  tmap :: Proxy xs -> Proxy c -> Proxy t -> (forall a . c a => t a -> Bool) -> [Property]

instance TypesToProps '[] c t where
  tmap _ _ _ _ = []

instance (c x, TypesToProps xs c t, Arbitrary (t x), Show (t x)) => TypesToProps (x ': xs) c t where
  tmap _ c t f = property (f :: t x -> Bool) : tmap (Proxy :: Proxy xs) c t f

main = defaultMain $ map (testProperty "zq_f_id") $ 
  tmap (Proxy :: Proxy '[3, 5, 7, 11]) 
       (Proxy :: Proxy KnownNat) 
       (Proxy :: Proxy Zq)
       prop_f

这可能不够通用,但不需要使用unsafeCoerce

当您编写unsafeCoerce prop_f时,类型检查器要求在它可以执行任何操作之前满足KnownNat q约束。您必须在结果类型上保留约束 - 这与您正在执行的操作基本上不兼容 - 或者首先擦除约束。

您可以将约束存储为数据(无论如何,它们都是内部存储的多态函数):

data Dict p where 
  Dict :: p => Dict p 

我们可以使用以下数据类型擦除约束:

anyDict :: Dict p 
anyDict = unsafeCoerce (Dict :: Dict (Eq ()))

prop_f' :: forall q . Zq q -> Bool
prop_f' q = case anyDict :: Dict (KnownNat q) of Dict -> prop_f q 

一般情况下这是不安全的,但在这种特定情况下它实际上是安全的,因为q中的Zq q是一个幻像参数,并且使用任何涉及Zq的内容无论如何都要选择一个具体的q,这样就可以删除类约束。

然后你可以写

class TypesToProps xs where
  tmap :: Proxy xs -> (forall a . a -> Bool) -> [Property]

instance TypesToProps '[] where
  tmap _ _ = []

instance (TypesToProps xs, Arbitrary x, Show x) => TypesToProps (x ': xs) where
  tmap _ f = property (f :: x -> Bool) : tmap (Proxy :: Proxy xs) f where

请注意,函数forall a . a -> Bool无人居住,因此您只能使用unsafe函数生成该类型的值。这意味着单独使用tmap是不可能用脚射击自己的;除非您使用不安全的函数,否则您甚至无法实例化tmap。最后:

main = defaultMain $ map (testProperty "zq_f_id") $ 
  tmap (Proxy :: Proxy '[Zq 3, Zq 5, Zq 7, Zq 11]) 
       (unsafeCoerce prop_f' :: forall a . a -> Bool)