2014-10-17 69 views
3

我試圖使用HSpec和QuickCheck來驗證屬性的猿(關聯性和標識元素)。我要驗證特定的實例,但想保留大部分代碼多態。這是我想出了幾個小時後:使用HSpec和QuickCheck驗證Data.Monoid屬性

module Test where 

import Test.Hspec 
import Test.QuickCheck 
import Data.Monoid 

instance (Arbitrary a) => Arbitrary (Sum a) where 
    arbitrary = fmap Sum arbitrary 

instance (Arbitrary a) => Arbitrary (Product a) where 
    arbitrary = fmap Product arbitrary 

prop_Monoid_mappend_mempty_x x = mappend mempty x === x 

sumMonoidSpec = it "mappend mempty x = x" $ property (prop_Monoid_mappend_mempty_x :: Sum Int -> Property) 
productMonoidSpec = it "mappend mempty x = x" $ property (prop_Monoid_mappend_mempty_x :: Product Double -> Property) 

main :: IO() 
main = hspec $ do 
    describe "Data.Monoid.Sum" $ do 
     sumMonoidSpec 
    describe "Data.Monoid.Product" $ do 
     productMonoidSpec 

我想吃點什麼,雖然是多態的

monoidSpec = it "mappend mempty x = x" $ property prop_Monoid_mappend_mempty_x 

,並指定實際含半幺羣實例(總和,產品)和式(智力,雙)以後。問題是它不會打字檢查。我不斷收到

src/[email protected]:42-18:50 No instance for (Arbitrary a0) arising from a use of property 
The type variable a0 is ambiguous 
Note: there are several potential instances: 
    instance Arbitrary a => Arbitrary (Product a) 
    -- Defined at /home/app/isolation-runner-work/projects/68426/session.207/src/src/Test.hs:10:10 
    instance Arbitrary a => Arbitrary (Sum a) 
    -- Defined at /home/app/isolation-runner-work/projects/68426/session.207/src/src/Test.hs:7:10 
    instance Arbitrary() -- Defined in Test.QuickCheck.Arbitrary 
    ...plus 27 others … 
src/[email protected]:51-18:79 No instance for (Monoid a0) 
    arising from a use of prop_Monoid_mappend_mempty_x 
The type variable a0 is ambiguous 
Note: there are several potential instances: 
    instance Monoid() -- Defined in Data.Monoid 
    instance (Monoid a, Monoid b) => Monoid (a, b) 
    -- Defined in Data.Monoid 
    instance (Monoid a, Monoid b, Monoid c) => Monoid (a, b, c) 
    -- Defined in Data.Monoid 
    ...plus 18 others … 

我知道我需要約束在多態版本的幺半羣是任意的,Eq和顯示,但我不知道如何。

問題是如何以多態方式表達Monoid的規格並避免代碼重複?

+0

您可能有興趣使用[hspec-laws](https://github.com/hspec/hspec-laws#readme)或[hspec-checkers](http://hackage.haskell.org/package/取而代之的是hspec-checkers)。 – 2014-11-01 05:26:20

回答

3

請注意類型property :: Testable prop => prop -> Property。類型變量prop被擦除,並且如果類型變量不再可用,則不會發生實例解析。基本上你想要做的是推遲instace選擇,並且要做到這一點,你必須使類型可用,直到你選擇實例。

一種方法是隨身攜帶一個額外的Proxy prop參數:

-- Possibly Uuseful helper function 
propertyP :: Testable prop => Proxy prop -> prop -> Property 
propertyP _ = property 

monoidProp :: forall m . (Arbitrary m, Testable m, Show m, Monoid m, Eq m) 
      => Proxy m -> Property 
monoidProp _ = property (prop_Monoid_mappend_mempty_x :: m -> Property) 

monoidSpec :: (Monoid m, Arbitrary m, Testable m, Show m, Eq m) => Proxy m -> Spec 
monoidSpec x = it "mappend mempty x = x" $ monoidProp x 

main0 :: IO() 
main0 = hspec $ do 
    describe "Data.Monoid.Sum" $ do 
     monoidSpec (Proxy :: Proxy (Sum Int)) 
    describe "Data.Monoid.Product" $ do 
     monoidSpec (Proxy :: Proxy (Product Double)) 

另一種方法是使用圖書館一樣tagged它提供了類型Tagged,它只是增加了一些假體類型參數,以現有類型:

import Data.Tagged 

type TaggedProp a = Tagged a Property 
type TaggedSpec a = Tagged a Spec 

monoidPropT :: forall a. (Monoid a, Arbitrary a, Show a, Eq a) 
      => TaggedProp a 
monoidPropT = Tagged (property (prop_Monoid_mappend_mempty_x :: a -> Property)) 

monoidSpecT :: forall a . (Monoid a, Arbitrary a, Show a, Eq a) => TaggedSpec a 
monoidSpecT = Tagged $ it "mappend mempty x = x" 
          (unTagged (monoidPropT :: TaggedProp a)) 

main1 :: IO() 
main1 = hspec $ do 
    describe "Data.Monoid.Sum" $ do 
     untag (monoidSpecT :: TaggedSpec (Sum Int)) 
    describe "Data.Monoid.Product" $ do 
     untag (monoidSpecT :: TaggedSpec (Product Double)) 

這些解決方案基本上是等價的,雖然在某些情況下,其中一個或另一個可能會更方便。由於我對你的用例不夠了解,所以我都包含了這兩個。

這兩個都只需要-XScopedTypeVariables

+0

我一直在玩你的解決方案,並且(糾正我,如果我錯了)看起來你實際上並不需要propertyP,因爲無論如何它忽略了第一個參數並且類型信息已經存在。拔出propertyP後,Proxy版本看起來幾乎與Tagged相同 - 這只是使用Proxies看起來更好一點的調用站點。 – maciekszajna 2014-10-17 21:36:07

+0

嚴格地說,你不需要'propertyP',但它強制實現了第一個和第二個參數具有相同類型的事實,這有時可以節省你編寫顯式類型簽名。 – user2407038 2014-10-18 01:44:40