请考虑以下代码:
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)
答案 0 :(得分:3)
首先,您的call
函数无法编译,也不应该编译:无法推断类型c x
中的Proxy c -> (forall a . c a => a -> Int) -> x -> Int
,因此您无法通过x
至f
。
接下来,您在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)