在Haskell中使用applicative functor时,我经常遇到这样的情况,我最终会得到这样的重复代码:
instance Arbitrary MyType where
arbitrary = MyType <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
在这个例子中,我想说:
instance Arbitrary MyType where
arbitrary = applyMany MyType 4 arbitrary
但我无法弄清楚如何制作applyMany
(或类似的东西)。我甚至无法弄清楚类型是什么,但它需要一个数据构造函数,一个Int (n),以及一个应用 n 次的函数。在为QuickCheck,SmallCheck,Data.Binary,Xml序列化和其他递归情况创建实例时会发生这种情况。
那我怎么定义applyMany
?
答案 0 :(得分:10)
结帐derive。任何其他好的泛型库也应该能够做到这一点;派生只是我熟悉的那个。例如:
{-# LANGUAGE TemplateHaskell #-}
import Data.DeriveTH
import Test.QuickCheck
$( derive makeArbitrary ''MyType )
为了解决你实际问过的问题,FUZxxl是对的,这在普通的Haskell中是不可能的。正如你所指出的,目前还不清楚它的类型应该是什么。有可能使用Template Haskell元编程(不太令人愉快)。如果你走这条路,你应该只使用一个已经为你做过艰苦研究的仿制库。我相信它也可以使用类型级自然和类型类,但不幸的是这种类型级解决方案通常难以抽象。 Conor McBride是working on that problem。
答案 1 :(得分:7)
我认为你可以用OverlappingInstances hack做到这一点:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, OverlappingInstances #-}
import Test.QuickCheck
import Control.Applicative
class Arbitrable a b where
convert :: Gen a -> Gen b
instance (Arbitrary a, Arbitrable b c) => Arbitrable (a->b) c where
convert a = convert (a <*> arbitrary)
instance (a ~ b) => Arbitrable a b where
convert = id
-- Should work for any type with Arbitrary parameters
data MyType a b c d = MyType a b c d deriving (Show, Eq)
instance Arbitrary (MyType Char Int Double Bool) where
arbitrary = convert (pure MyType)
check = quickCheck ((\s -> s == s) :: (MyType Char Int Double Bool -> Bool))
答案 2 :(得分:6)
对我的另一个答案不满意,我想出了一个令人敬畏的答案。
-- arb.hs
import Test.QuickCheck
import Control.Monad (liftM)
data SimpleType = SimpleType Int Char Bool String deriving(Show, Eq)
uncurry4 f (a,b,c,d) = f a b c d
instance Arbitrary SimpleType where
arbitrary = uncurry4 SimpleType `liftM` arbitrary
-- ^ this line is teh pwnzors.
-- Note how easily it can be adapted to other "simple" data types
ghci> :l arb.hs
[1 of 1] Compiling Main ( arb.hs, interpreted )
Ok, modules loaded: Main.
ghci> sample (arbitrary :: Gen SimpleType)
>>>a bunch of "Loading package" statements<<<
SimpleType 1 'B' False ""
SimpleType 0 '\n' True ""
SimpleType 0 '\186' False "\208! \227"
...
我如何理解这一点的冗长解释
所以这就是我得到它的方式。我想知道,“那么Arbitrary
已经有(Int, Int, Int, Int)
实例了吗?我确定没有人写它,所以它必须以某种方式得出。果然,我在docs for instances of Arbitrary中找到了以下内容:
(Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)
好吧,如果他们已经定义了那么,为什么不滥用它呢?仅由较小的任意数据类型组成的简单类型与仅仅元组没有太大区别。
所以现在我需要以某种方式转换4元组的“任意”方法,以便它适用于我的类型。可能涉及到不受影响。
停止。 Hoogle时间!
(我们可以很容易地定义我们自己的uncurry4
,所以我们假设我们已经开始使用它了。)
我有一个生成器,arbitrary :: Gen (q,r,s,t)
(其中q,r,s,t都是任意的实例)。但我们只能说它是arbitrary :: Gen a
。换句话说,a
代表(q,r,s,t)
。我有一个函数uncurry4
,其类型为(q -> r -> s -> t -> b) -> (q,r,s,t) -> b
。我们显然会将uncurry4应用于SimpleType
构造函数。因此uncurry4 SimpleType
的类型为(q,r,s,t) -> SimpleType
。但是,让我们保持返回值的通用性,因为Hoogle不知道我们的SimpleType。所以记住我们对a
的定义,我们基本上uncurry4 SimpleType :: a -> b
。
所以我有一个Gen a
和一个函数a -> b
。我想要一个Gen b
结果。 (请记住,对于我们的情况,a
是(q,r,s,t)
而b
是SimpleType
)。所以我正在寻找具有此类型签名的函数:Gen a -> (a -> b) -> Gen b
。 Hoogling that,并且知道Gen
是Monad
的一个实例,我立刻认出liftM
是解决我问题的唯一神奇方法。
Hoogle再次拯救了这一天。我知道可能有一些“提升”组合器可以获得理想的结果,但老实说我没想到会使用liftM(durrr!),直到我用类型签名为止。
答案 3 :(得分:5)
这至少是我得到的:
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module ApplyMany where
import Control.Applicative
import TypeLevel.NaturalNumber -- from type-level-natural-number package
class GetVal a where
getVal :: a
class Applicative f => ApplyMany n f g where
type Res n g
app :: n -> f g -> f (Res n g)
instance Applicative f => ApplyMany Zero f g where
type Res Zero g = g
app _ fg = fg
instance
(Applicative f, GetVal (f a), ApplyMany n f g)
=> ApplyMany (SuccessorTo n) f (a -> g)
where
type Res (SuccessorTo n) (a -> g) = Res n g
app n fg = app (predecessorOf n) (fg<*>getVal)
用法示例:
import Test.QuickCheck
data MyType = MyType Char Int Bool deriving Show
instance Arbitrary a => GetVal (Gen a) where getVal = arbitrary
test3 = app n3 (pure MyType) :: Gen MyType
test2 = app n2 (pure MyType) :: Gen (Bool -> MyType)
test1 = app n1 (pure MyType) :: Gen (Int -> Bool -> MyType)
test0 = app n0 (pure MyType) :: Gen (Char -> Int -> Bool -> MyType)
不过,我认为这个解决方案在现实世界中并不是很有用。特别是没有本地类型。
答案 4 :(得分:4)
结帐liftA2 and liftA3。此外,您可以轻松编写自己的applyTwice或applyThrice方法,如下所示:
applyTwice :: (a -> a -> b) -> a -> b
applyTwice f x = f x x
applyThrice :: (a -> a -> a -> b) -> a -> b
applyThrice f x = f x x x
我没有简单的方法来获得你要求的通用applyMany,但写这些琐碎的助手既不困难也不常见。
[编辑]事实证明,你认为这样的事情会起作用
liftA4 f a b c d = f <$> a <*> b <*> c <*> d
quadraApply f x = f x x x x
data MyType = MyType Int String Double Char
instance Arbitrary MyType where
arbitrary = (liftA4 MyType) `quadraApply` arbitrary
但事实并非如此。 (liftA4 MyType)
的类型签名为(Applicative f) => f Int -> f String -> f Double -> f Char -> f MyType
。这与quadraApply的第一个参数不兼容,quadraApply的类型签名为(a -> a -> a -> a -> b) -> a -> b
。它只适用于包含多个相同任意类型值的数据结构。
data FourOf a = FourOf a a a a
instance (Arbitrary a) => Arbitrary (FourOf a) where
arbitrary = (liftA4 FourOf) `quadraApply` arbitrary
ghci> sample (arbitrary :: Gen (FourOf Int))
当然,如果遇到这种情况,你可以这样做
ghci> :l +Control.Monad
ghci> let uncurry4 f (a, b, c, d) = f a b c d
ghci> samples <- sample (arbitrary :: Gen (Int, Int, Int, Int))
ghci> forM_ samples (print . uncurry4 FourOf)
可能有一些语言编译指示可以将“任意”函数强加到更多样化的数据类型中。但那目前超出了我的Haskell-fu水平。
答案 5 :(得分:2)
Haskell无法做到这一点。问题是,你的函数将有一个类型,这取决于数字参数。对于允许依赖类型的类型系统,这应该是可能的,但我想不是在Haskell中。
您可以尝试使用多态和tyeclasses来实现这一点,但它可能会变得很糟糕,您需要一大堆扩展来满足编译器。