packages feed

rebound-0.1.0.0: examples/LCQC.hs

-- |
-- Module      : LCQC
-- Description : Generators for well-scoped lambda calculus terms
-- Stability   : experimental
--
-- This module demonstrates the use of well-scoped lambda calculus terms
-- with QuickCheck. It then demonstrates how to use QC to test the normalization
-- functions in the `LC` module.
module LCQC where

import LC
import Test.QuickCheck
import Rebound
import Rebound.Bind.Single
import Data.Fin
import Data.Maybe as Maybe

----------------------------------------------
-- Generating well-scoped expressions
----------------------------------------------

-- | Generate an expression in scope `n`, using
-- size parameter sz
-- >>> sample' (genExp s3 10)
-- [(λ. (λ. (0 2))),(((λ. 0) (1 2)) ((0 2) 2)),(((2 1) 0) (1 1)),1,((λ. 2) (2 (λ. 1))),(λ. 2),2,0,((λ. (0 1)) (λ. (1 3))),(((1 2) 2) (λ. (0 2))),(λ. 2)]
genExp :: forall n. SNat n -> Int -> Gen (Exp n)
genExp n sz =
    let
        genLam = Lam <$> (bind <$> genExp (next n) (sz `div` 2))
        genApp = App <$> genExp n (sz `div` 2) <*> genExp n (sz `div` 2)
    in
    case snat_ n of
       SZ_ -> if sz <= 1
                then pure $ Lam (bind (Var 0))  -- smallest closed term
                else oneof [genLam, genApp]     -- closed term, no vars
       SS_ x ->
         let
            genVar = withSNat x $ elements $ map Var universe
         in
            if sz <= 1
              then genVar
              else oneof [genVar, genLam, genApp]

-- | shrink a lambda calculus term, maintaining the same scope.
shrinkExp :: SNatI n => Exp n -> [Exp n]
shrinkExp (Var FZ) = []
shrinkExp (Var x ) = [ Var (pred x) ]
shrinkExp (Lam t)  = [ Lam (bind t') | t' <- shrinkExp (getBody t) ]
shrinkExp (App f a) =
  [f,a] ++ [ App f' a | f' <- shrinkExp f]
        ++ [ App f a' | a' <- shrinkExp a]

-- | arbitrary instance for lambda calculus terms
instance SNatI n => Arbitrary (Exp n) where
  arbitrary :: SNatI n => Gen (Exp n)
  arbitrary = sized (genExp snat)
  shrink :: SNatI n => Exp n -> [Exp n]
  shrink = shrinkExp


----------------------------------------------
-- Property-based testing example
----------------------------------------------

{-

Let's use QuickCheck to make sure that our various
normalization functions for the lambda calculus all produce the
same result.

However, we need to deal with the fact that not all lambda
calculus terms normalize. Therefore, we will instruct QC to discard
the test case if the expression does not normalize within 0.1 seconds.
We will also discard expressions that are already in normal form.
-}

prop_normalize :: (Exp n -> Exp n) -> Exp n -> Property
prop_normalize f e = discardAfter 100000 $
    e /= e' ==> e' == f e
   where e' = nf e

prop_nf1 :: Exp Z -> Property
prop_nf1 = prop_normalize nf1

prop_nfEnv :: Exp Z -> Property
prop_nfEnv = prop_normalize nfEnv