7

在 Haskell 中使用 applicative functors 时,我经常遇到这样的重复代码:

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呢?

4

6 回答 6

10

查看派生。任何其他好的泛型库也应该能够做到这一点;派生只是我熟悉的一个。例如:

{-# LANGUAGE TemplateHaskell #-}
import Data.DeriveTH
import Test.QuickCheck

$( derive makeArbitrary ''MyType )

为了解决您实际提出的问题,FUZxxl 是对的,这在普通的香草 Haskell 中是不可能的。正如您所指出的,甚至不清楚它的类型应该是什么。模板 Haskell 元编程是可能的(不太愉快)。如果你走这条路,你可能应该只使用一个已经为你完成了艰苦研究的泛型库。我相信使用类型级别的自然和类型类也是可能的,但不幸的是,这种类型级别的解决方案通常难以抽象。Conor McBride正在解决这个问题

于 2011-01-21T05:25:32.960 回答
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))
于 2011-01-21T13:22:13.510 回答
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)?我确定没有人写过它,所以它必须以某种方式派生出来。果然,我在文档中找到了 Arbitrary 实例的以下内容:

(Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)

好吧,如果他们已经定义了,那为什么不滥用它呢?仅由较小的任意数据类型组成的简单类型与元组没有太大区别。

所以现在我需要以某种方式转换 4 元组的“任意”方法,使其适用于我的类型。可能涉及到 Uncurrying。

停止。胡歌时间!

(我们可以很容易地定义自己的uncurry4,所以假设我们已经有了它可以操作。)

我有一个生成器arbitrary :: Gen (q,r,s,t)(其中 q,r,s,t 都是 Arbitrary 的实例)。但我们只是说它是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结果。(请记住,对于我们的情况,ais(q,r,s,t)bis SimpleType)。所以我正在寻找具有这种类型签名的函数:Gen a -> (a -> b) -> Gen b. 搜索,并知道这Gen是 的一个实例Monad,我立即认识到liftM这是解决我的问题的单子魔法解决方案。

Hoogle 再次挽救了局面。我知道可能有一些“提升”组合器来获得所需的结果,但老实说,直到我 hoog 了类型签名,我才想到使用 liftM(durrr!)。

于 2011-01-27T04:55:10.390 回答
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)

顺便说一句,我认为这个解决方案在现实世界中不是很有用。特别是没有本地类型类。

于 2011-01-21T14:55:18.240 回答
4

检查liftA2 和 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 水平。

于 2011-01-21T06:50:52.927 回答
2

这在 Haskell 中是不可能的。问题是,您的函数将有一个类型,这取决于数字参数。使用允许依赖类型的类型系统,这应该是可能的,但我猜不是在 Haskell 中。

你可以尝试使用多态性和 tyeclasses 来实现它,但它可能会变得很笨拙,你需要一大堆扩展来满足编译器的要求。

于 2011-01-21T05:07:13.320 回答