7

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

4

6 に答える 6

10

派生をチェックしてください。他の優れたジェネリック ライブラリでも、これを実行できるはずです。派生は私がよく知っているものです。例えば:

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

$( derive makeArbitrary ''MyType )

あなたが実際に尋ねた質問に答えるために、FUZxxlは正しいです、これは普通のバニラHaskellでは不可能です。ご指摘のとおり、その型がどうあるべきかは明らかではありません。Template Haskell メタプログラミングで可能です (あまり快適ではありません)。そのルートに行く場合は、おそらく、すでにハードな調査を行っているジェネリック ライブラリを使用する必要があります。型レベルのナチュラルと型クラスを使用することも可能だと思いますが、残念ながら、そのような型レベルのソリューションは通常、抽象化するのが困難です。Conor McBride はその問題に取り組んでいます

于 2011-01-21T05:25:32.960 に答える
7

OverlappingInstances ハックでできると思います:

{-# 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 a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)

ええと、彼らがすでにそれを定義しているのなら、それを悪用してみませんか? より小さな Arbitrary データ型で構成されているだけの単純な型は、単なるタプルと大差ありません。

したがって、4タプルの「任意の」メソッドを何らかの方法で変換して、自分のタイプで機能するようにする必要があります。Uncurring が関与している可能性があります。

止まる。ホーグルタイム!

(独自の を簡単に定義できる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コンストラクターに適用することは明らかです。typeuncurry4 SimpleTypeも同様(q,r,s,t) -> SimpleTypeです。ただし、Hoogle は SimpleType を認識していないため、戻り値は一般的なものにしておきましょう。の定義を思い出すとa、本質的に が得られuncurry4 SimpleType :: a -> bます。

だから私は aGen aと functionを持っていますa -> b。そして結果が欲しいGen ba(私たちの状況では、 is(q,r,s,t)bisであることを思い出してくださいSimpleType)。だから私はこの型シグネチャを持つ関数を探しています: Gen a -> (a -> b) -> Gen b. それをフーグリングしGen、それが のインスタンスであることを知って、私は自分の問題に対するモナド的で魔法のような解決策であるMonadとすぐに認識します。liftM

Hoogle は再び危機を脱します。望ましい結果を得るには、おそらく何らかの「リフティング」コンビネータがあることは知っていましたが、正直なところ、型シグネチャを調べるまでは、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の最初のパラメーターと互換性がありません(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 に答える