繁体   English   中英

我如何抽象一个常见的Haskell递归应用函子模式

[英]How can I abstract a common Haskell recursive applicative functor pattern

在Haskell中使用可应用函子时,我经常遇到这样的情况:我最终得到这样的重复代码:

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

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

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

$( derive makeArbitrary ''MyType )

为了解决您实际提出的问题,FUZxxl是正确的,这在纯香草Haskell中是不可能的。 如您所指出的,尚不清楚它的类型应该是什么。 Template Haskell元编程是可能的(不太愉快)。 如果走那条路,您可能应该只使用已经为您进行了艰苦研究的泛型库。 我相信也可以使用类型级别的自然类型和类型类,但是不幸的是,此类类型级别的解决方案通常很难抽象出来。 Conor McBride 正在研究这个问题

我认为您可以使用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))

对其他答案不满意,我想出了一个很棒的答案。

-- 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"
...

关于我如何解决的冗长解释

所以这就是我的方法。 我想知道,“那么(Int, Int, Int, Int)已经有一个Arbitrary实例了吗?我确定没有人写过它,因此必须以某种方式派生。当然,我在文档中找到了以下内容对于任意情况

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

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

因此,现在我需要以某种方式将4元组的“任意”方法转换为适用于我的类型的方法。 可能会涉及到无忧无虑。

停止。 笨拙的时间!

(我们可以轻松定义自己的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和一个函数Gen a a -> b 我想要Gen b结果。 (请记住,对于我们的情况, a(q,r,s,t)bSimpleType )。 所以我正在寻找一个具有这种类型签名的函数: Gen a -> (a -> b) -> Gen b 了解这一点 ,并知道GenMonad一个实例,因此我立即认识到liftM是解决我的问题的一元魔术。

Hoogle再次节省了一天。 我知道可能会有一些“提升”组合器来获得理想的结果,但是老实说,直到我仔细检查类型签名之前,我才认为不使用liftM(durrr!)。

这至少是我得到的:

{-# 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)

顺便说一句,我认为这种解决方案在现实世界中不是很有用。 尤其是没有本地类型类的情况。

检出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的简单方法,但是编写诸如此类的琐碎帮助程序既不困难也不罕见。


[edit]原来,您会认为类似的事情会起作用

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 (liftA4 MyType) (Applicative f) => f Int -> f String -> f Double -> f Char -> f MyType 这与quadraApply的第一个参数不兼容,后者的类型签名为(a -> a -> a -> a -> b) -> a -> b a-> a-> a-> a- (a -> a -> a -> a -> b) -> a -> b a- (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水平。

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

您可以尝试使用多态性和tyeclasss对此进行归档,但是它可能会变得很笨拙,您需要大量扩展才能满足编译器的要求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM