2

I'm in a situation where I have a data type like

data X = X {foo :: SInteger, bar :: SInteger}

and I want to prove e.g.

forAll_ $ \x -> foo x + bar x .== bar x + foo x

using haskell's sbv. This doesn't compile because X -> SBool is not an instance of Provable. I can make it an instance with e.g.

instance (Provable p) => Provable (X -> p) where
  forAll_ k = forAll_ $ \foo bar -> forAll_ $ k $ X foo bar
  forAll (s : ss) k =
    forAll ["foo " ++ s, "bar " ++ s] $ \foo bar -> forAll ss $ k $ X foo bar
  forAll [] k = forAll_ k
  -- and similarly `forSome_` and `forSome`

but this is tedious and error prone (e.g. using forSome when forAll should've been used). Is there a way to automatically derive Provable for my type?

  • 1
    You could write a Template Haskell helper. – Benjamin Hodgson Oct 11 '16 at 13:35
  • I've only used template haskell as a client before. My question is basically whether this is already built into SBV. – Martin Bidlingmaier Oct 11 '16 at 14:02
  • Scouring the source of the package, doesn't look like what you are looking for is there. That said, I'm not sure I understand why you don't just use a tuple. If you really want the type distinction, that is what `GeneralizedNewtypeDeriving` is for. – Alec Oct 11 '16 at 15:19
  • 1
    There is an instance of the form `Provable ((SBV a, SBV b) -> p)` and your type `X` is isomorphic to `(SBV Integer, SBV Integer)`. – user2407038 Oct 11 '16 at 15:21

2 Answers2

4

It can at least be made less error-prone:

onX :: (((SInteger, SInteger) -> a) -> b) -> ((X -> a) -> b)
onX f g = f (g . uncurry X)

instance Provable p => Provable (X -> p) where
    forAll_  = onX forAll_
    forSome_ = onX forSome_
    forAll   = onX . forAll
    forSome  = onX . forSome

There's also a generalizable pattern, in case SBV's existing instances for up to 7-tuples are not sufficient.

data Y = Y {a, b, c, d, e, f, g, h, i, j :: SInteger}
-- don't try to write the types of these, you will wear out your keyboard
fmap10 = fmap . fmap . fmap . fmap . fmap . fmap . fmap . fmap . fmap . fmap
onY f g = f (fmap10 g Y)

instance Provable p => Provable (Y -> p) where
    forAll_  = onY forAll_
    forSome_ = onY forSome_
    forAll   = onY . forAll
    forSome  = onY . forSome

Still tedious, though.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • Care to explain the intuition behind the generalization? As a side note, you missed the incredible opportunity to write `fmap10 = (.).(.).(.).(.).(.).(.).(.).(.).(.).(.)`. – Alec Oct 12 '16 at 02:20
  • 1
    @Alec Maybe not intuition, but the mechanics go like this: since there is a `Provable (SInteger -> p)` instance lying around, this means that functions with any number of `SInteger` arguments are already handled. To use that instance to make a `Provable (Y -> p)` instance, we'd like to compose our `Y -> p` function with the `SInteger -> SInteger -> ... -> Y` function we already have (namely, the constructor `Y`) to make a `SInteger -> SInteger -> ... -> p` function. The chain of `fmap`s (or `(.)`s, as you observe) does this composition. – Daniel Wagner Oct 12 '16 at 04:48
1

Daniel's answer is "as good as it gets" if you really want to use quantifiers directly with your lambda-expressions. However, instead of creating a Provable instance, I'd strongly recommend defining a variant of free for your type:

freeX :: Symbolic X
freeX = do f <- free_
           b <- free_
           return $ X f b

Now you can use it like this:

test = prove $ do x <- freeX
                  return $ foo x + bar x .== bar x + foo x

This is much easier to use, and composes well with constraints. For instance, if your data type has the extra constraint that both components are positive, and the first one is larger than the second, then you can write freeX thusly:

freeX :: Symbolic X
freeX = do f <- free_
           b <- free_
           constrain $ f .> b
           constrain $ b .> 0
           return $ X f b

Note that this will work correctly in both prove and sat contexts, since free knows how to behave correctly in each case.

I think this is much more readable and easier to use, even though it forces you to use the do-notation. You can also create a version that accepts names, like this:

freeX :: String -> Symbolic X
freeX nm = do f <- free $ nm ++ "_foo"
              b <- free $ nm ++ "_bar"
              constrain $ f .> b
              constrain $ b .> 0
              return $ X f b

test = prove $ do x <- freeX "x"
                  return $ foo x + bar x .== bar x * foo x

Now we get:

*Main> test
Falsifiable. Counter-example:
  x_foo = 3 :: Integer
  x_bar = 1 :: Integer

You can also make X "parseable" by SBV. In this case the full code looks like this:

data X = X {foo :: SInteger, bar :: SInteger} deriving Show

freeX :: Symbolic X
freeX = do f <- free_
           b <- free_
           return $ X f b

instance SatModel X where
  parseCWs xs = do (x, ys) <- parseCWs xs
                   (y, zs) <- parseCWs ys
                   return $ (X (literal x) (literal y), zs)

The following test demonstrates:

test :: IO (Maybe X)
test = extractModel `fmap` (prove $ do
                x <- freeX
                return $ foo x + bar x .== bar x * foo x)

We have:

*Main> test >>= print
Just (X {foo = -4 :: SInteger, bar = -5 :: SInteger})

Now you can take your counter-examples and post-process them as you wish.

alias
  • 28,120
  • 2
  • 23
  • 40