Skip to content

Commit f270750

Browse files
Optimise how we deal with either specs (#54)
1 parent df5f8f4 commit f270750

File tree

10 files changed

+68
-8
lines changed

10 files changed

+68
-8
lines changed

bench/Constrained/Bench.hs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
module Constrained.Bench where
1111

1212
import Constrained.API
13+
import Constrained.Generation
14+
import Constrained.Examples.Set
15+
import Constrained.Examples.Map
16+
import Constrained.Examples.Basic
17+
1318
import Control.DeepSeq
1419
import Criterion
1520
import Data.Map (Map)
@@ -30,6 +35,10 @@ benchmarks =
3035
(giveHint (Nothing, 30) <> trueSpec :: Specification (Tree Int))
3136
, benchSpec 10 30 "roseTreeMaybe" roseTreeMaybe
3237
, benchSpec 10 30 "listSumPair" listSumPair
38+
, benchSpec 10 30 "maybeJustSetSpec" maybeJustSetSpec
39+
, benchSpec 10 40 "eitherKeys" eitherKeys
40+
, benchSimplifySpec "eitherKeys" eitherKeys
41+
, benchSpec 10 40 "chooseBackwards" chooseBackwards'
3342
]
3443

3544
roseTreeMaybe :: Specification (Tree (Maybe (Int, Int)))
@@ -49,6 +58,11 @@ listSumPair = constrained $ \xs ->
4958
, forAll' xs $ \x y -> [20 <. x, x <. 30, y <. 100]
5059
]
5160

61+
benchSimplifySpec :: HasSpec a => String -> Specification a -> Benchmark
62+
benchSimplifySpec nm spec =
63+
bench ("simplify/" ++ nm) $
64+
nf (show . simplifySpec) spec
65+
5266
benchSpec :: (HasSpec a, NFData a) => Int -> Int -> String -> Specification a -> Benchmark
5367
benchSpec seed size nm spec =
5468
bench (unlines [nm, show (genFromSpecWithSeed seed size spec)]) $

constrained-generators.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ benchmark bench
163163
build-depends:
164164
base,
165165
constrained-generators,
166+
constrained-generators:examples,
166167
containers,
167168
criterion,
168169
deepseq,

examples/Constrained/Examples/Map.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,14 @@ eitherKeys = constrained' $ \ [var| as |] [var| bs |] [var| m |] ->
138138
k `elem_` ls ++. rs
139139
]
140140
]
141+
142+
keysExample :: Specification (Either Int Int)
143+
keysExample = constrained $ \ k ->
144+
[ caseOn k
145+
(branch $ \ a -> a `elem_` as)
146+
(branch $ \ b -> b `elem_` bs)
147+
, reify as (map Left) $ \ ls ->
148+
reify bs (map Right) $ \ rs ->
149+
k `elem_` ls ++. rs
150+
] where as = lit [ 1 .. 10]
151+
bs = lit [ 11 .. 20 ]

src/Constrained/Generation.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ import Prelude hiding (cycle, pred)
101101
-- generators are not flexible enough.
102102
genFromSpecT ::
103103
forall a m. (HasCallStack, HasSpec a, MonadGenError m) => Specification a -> GenT m a
104+
genFromSpecT (ExplainSpec [] s) = genFromSpecT s
105+
genFromSpecT (ExplainSpec es s) = push es (genFromSpecT s)
104106
genFromSpecT (simplifySpec -> spec) = case spec of
105107
ExplainSpec [] s -> genFromSpecT s
106108
ExplainSpec es s -> push es (genFromSpecT s)
@@ -947,7 +949,6 @@ genFromPreds env0 (optimisePred . optimisePred -> preds) = do
947949
(env', plan') <- stepPlan origPlan env plan
948950
go env' plan'
949951
go env0 origPlan
950-
where
951952

952953
-- | Push as much information we can backwards through the plan.
953954
backPropagation :: Set Name -> SolverPlan -> SolverPlan
@@ -1050,7 +1051,10 @@ pinnedBy :: forall a. HasSpec a => Var a -> Pred -> Maybe (Term a)
10501051
pinnedBy x (Assert (Equal t t'))
10511052
| V x' <- t, Just Refl <- eqVar x x' = Just t'
10521053
| V x' <- t', Just Refl <- eqVar x x' = Just t
1054+
pinnedBy x (ElemPred True (V x') (xs NE.:| []))
1055+
| Just Refl <- eqVar x x' = Just (lit xs)
10531056
pinnedBy x (And ps) = listToMaybe $ catMaybes $ map (pinnedBy x) ps
1057+
pinnedBy x (Explain _ p) = pinnedBy x p
10541058
pinnedBy _ _ = Nothing
10551059

10561060
-- ==================================================================================================

src/Constrained/NumOrd.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,6 @@ caseBoolSpec spec cont = case possibleValues spec of
10631063
[b] -> cont b
10641064
_ -> mempty
10651065
where
1066-
-- where possibleValues s = filter (flip conformsToSpec (simplifySpec s)) [True, False]
10671066
-- This will always get the same result, and probably faster since running 2
10681067
-- conformsToSpec on True and False takes less time than simplifying the spec.
10691068
-- Since we are in TheKnot, we could keep the simplifySpec. Is there a good reason to?

src/Constrained/Spec/List.hs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
{-# LANGUAGE TypeOperators #-}
1818
{-# LANGUAGE UndecidableSuperClasses #-}
1919
{-# LANGUAGE ViewPatterns #-}
20-
{-# OPTIONS_GHC -Wno-orphans #-}
20+
{-# OPTIONS_GHC -Wno-orphans -Wno-name-shadowing #-}
2121

2222
-- | `TypeSpec` definition for `[]` and functions for writing constraints over
2323
-- lists
@@ -227,7 +227,8 @@ instance HasSpec a => HasSpec [a] where
227227
Just szHint -> do
228228
sz <- genFromSizeSpec (leqSpec szHint)
229229
listOfUntilLenT (genFromSpecT elemS) (fromIntegral sz) (const True)
230-
pureGen $ shuffle (must ++ lst)
230+
must' <- pureGen $ shuffle must
231+
pureGen $ randomInterleaving must' lst
231232
genFromTypeSpec (ListSpec msz must szSpec elemS NoFold) = do
232233
sz0 <- genFromSizeSpec (szSpec <> geqSpec (sizeOf must) <> maybe TrueSpec (leqSpec . max 0) msz)
233234
let sz = fromIntegral (sz0 - sizeOf must)
@@ -236,7 +237,8 @@ instance HasSpec a => HasSpec [a] where
236237
(genFromSpecT elemS)
237238
sz
238239
((`conformsToSpec` szSpec) . (+ sizeOf must) . fromIntegral)
239-
pureGen $ shuffle (must ++ lst)
240+
must' <- pureGen $ shuffle must
241+
pureGen $ randomInterleaving must' lst
240242
genFromTypeSpec (ListSpec msz must szSpec elemS (FoldSpec f foldS)) = do
241243
let szSpec' = szSpec <> maybe TrueSpec (leqSpec . max 0) msz
242244
genFromFold must szSpec' elemS f foldS
@@ -266,6 +268,21 @@ instance HasSpec a => HasSpec [a] where
266268
<> satisfies (sizeOf_ x) size
267269
<> maybe TruePred (flip genHint x) msz
268270

271+
randomInterleaving :: [a] -> [a] -> Gen [a]
272+
randomInterleaving xs ys = go xs ys (length ys)
273+
where
274+
go [] ys _ = pure ys
275+
go xs [] _ = pure xs
276+
go xs ys l = do
277+
-- TODO: think about distribution here
278+
i <- choose (0, l)
279+
go' i xs ys (l - i)
280+
281+
go' _ xs [] _ = pure xs
282+
go' _ [] ys _ = pure ys
283+
go' 0 (x:xs) ys l = (x:) <$> go xs ys l
284+
go' i xs (y:ys) l = (y:) <$> go' (i-1) xs ys l
285+
269286
instance HasSpec a => HasGenHint [a] where
270287
type Hint [a] = Integer
271288
giveHint szHint = typeSpec $ ListSpec (Just szHint) [] mempty mempty NoFold

src/Constrained/Spec/Map.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ instance
179179
fold
180180
[ maybe TrueSpec (leqSpec . max 0) mHint
181181
, size
182-
, maxSpec (cardinality (fstSpec kvs)) -- (mapSpec FstW (mapSpec ToGenericW kvs)))
182+
, maxSpec (cardinality (fstSpec kvs))
183183
, maxSpec (cardinalTrueSpec @k)
184184
, geqSpec 0
185185
]

src/Constrained/Syntax.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE ExistentialQuantification #-}
2+
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE FlexibleContexts #-}
34
{-# LANGUAGE FlexibleInstances #-}
45
{-# LANGUAGE GADTs #-}
@@ -721,6 +722,7 @@ mkCase tm cs
721722
| Semigroup.getAll $ foldMapList isTrueBinder cs = TruePred
722723
| Semigroup.getAll $ foldMapList (isFalseBinder . thing) cs = FalsePred (pure "mkCase on all False")
723724
| Lit a <- tm = runCaseOn a (mapList thing cs) (\x val p -> substPred (Env.singleton x val) p)
725+
| Just es <- buildElemList cs = ElemPred True tm es
724726
| otherwise = Case tm cs
725727
where
726728
isTrueBinder (Weighted Nothing (_ :-> TruePred)) = Semigroup.All True
@@ -729,6 +731,17 @@ mkCase tm cs
729731
isFalseBinder (_ :-> FalsePred {}) = Semigroup.All True
730732
isFalseBinder _ = Semigroup.All False
731733

734+
buildElemList :: List (Weighted Binder) as -> Maybe (NE.NonEmpty (SumOver as))
735+
buildElemList Nil = Nothing
736+
buildElemList (Weighted Nothing (x :-> ElemPred True (V x') as) :> xs)
737+
| Just Refl <- eqVar x x' =
738+
case xs of
739+
Nil -> Just as
740+
_ :> _ -> do
741+
rest <- buildElemList xs
742+
return $ fmap SumLeft as <> fmap SumRight rest
743+
buildElemList _ = Nothing
744+
732745
-- | Run a `caseOn`
733746
runCaseOn ::
734747
SumOver as ->

src/Constrained/Test.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ prop_constrained_satisfies_sound spec = prop_sound (constrained $ \a -> satisfie
7575
-- | Check that explanations don't immediately ruin soundness
7676
prop_constrained_explained :: HasSpec a => Specification a -> QC.Property
7777
prop_constrained_explained spec =
78-
QC.forAll QC.arbitrary $ \es ->
79-
prop_sound $ constrained $ \x -> Explain es $ x `satisfies` spec
78+
let es = NE.singleton "Dummy explanation"
79+
in prop_sound $ constrained $ \x -> Explain es $ x `satisfies` spec
8080

8181
-- | `prop_complete ps` assumes that `ps` is satisfiable and checks that it doesn't crash
8282
prop_complete :: HasSpec a => Specification a -> QC.Property

test/Constrained/Tests.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ tests nightly =
205205
testSpec "composeEvenSpec" composeEvenSpec
206206
testSpec "oddSpec" oddSpec
207207
testSpec "composeOddSpec" composeOddSpec
208+
testSpec "keysExample" keysExample
208209

209210
negativeTests :: Spec
210211
negativeTests =

0 commit comments

Comments
 (0)