packages feed

free-foil-0.5.0: test/Control/Monad/Foil/TelescopeLawsSpec.hs

{-# LANGUAGE DataKinds  #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs      #-}
{-# LANGUAGE LambdaCase      #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes      #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | The laws of "Control.Monad.Foil.Laws" for telescopes, and one law that
-- only patterns with payloads have: refreshing a telescope transports its
-- payloads along the refreshed binders ('Foil.PatternTransport'). Payloads
-- are names or terms of the λ-calculus of "Control.Monad.Free.Foil.Example".
module Control.Monad.Foil.TelescopeLawsSpec (spec) where

import           Test.Hspec
import           Test.QuickCheck

import           Control.Monad.Foil
import           Control.Monad.Foil.Internal           (Name (..))
import           Control.Monad.Foil.Laws
import           Control.Monad.Foil.Relative           (RelMonad)
import           Control.Monad.Foil.Telescope          (Telescope (..))
import           Control.Monad.Free.Foil               (AST (..), substitute)
import           Control.Monad.Free.Foil.Example       (Expr, ExprF, pattern AppE, pattern LamE)
import           Control.Monad.Free.Foil.ExampleSyntax (exprSyntax)
import           Control.Monad.Free.Foil.Laws

-- | Telescopes of up to three steps, with payloads from a generator. The
-- telescope stops early where there is no payload to give.
genTelescope :: forall e. (forall n. Ctx n -> Gen (Maybe (e n))) -> GenPattern (Telescope () e)
genTelescope genE ctx0 = do
  k <- chooseInt (0, 3)
  go k ctx0
  where
    go :: Int -> Ctx n -> Gen (PatIn (Telescope () e) n)
    go k ctx = withCtx ctx $ \scope ->
      if k <= 0
        then pure (PatIn TelescopeEmpty)
        else genE ctx >>= \case
          Nothing -> pure (PatIn TelescopeEmpty)
          Just payload ->
            withHintedBinder scope $ \binder -> do
              PatIn rest <- go (k - 1) (extendCtx ctx binder)
              pure (PatIn (TelescopeCons () payload binder rest))

-- | The binders of a telescope, in order.
telescopeNames :: PatternNames (Telescope label e)
telescopeNames = \case
  TelescopeEmpty              -> []
  TelescopeCons _ _ b rest    -> nameId (nameOf b) : telescopeNames rest

-- | Payloads that are names of the scope before the step, if it has any.
genNamePayload :: Ctx n -> Gen (Maybe (Name n))
genNamePayload ctx = case ctxNames ctx of
  [] -> pure Nothing
  xs -> Just <$> elements xs

sg :: SyntaxGen NameBinder ExprF
sg = exprSyntax genNameBinder nameBinderNames

-- | Payloads that are λ-terms.
genTermPayload :: Ctx n -> Gen (Maybe (Expr n))
genTermPayload ctx = Just <$> sized (genAST sg ctx . min 8)

-- | A telescope with a body, as one λ-term: each step @(x : A)@ becomes
-- @A (λx. …)@, so that α-equivalence of the encodings is α-equivalence of
-- the telescopes with their bodies.
encode :: (forall x. e x -> Expr x) -> Telescope () e n l -> Expr l -> Expr n
encode f = \case
  TelescopeEmpty -> id
  TelescopeCons () payload binder rest -> \body ->
    AppE (f payload) (LamE binder (encode f rest body))

-- | A telescope with a body, decoded from its encoding.
data Decoded e n where
  Decoded :: Telescope () e n l -> Expr l -> Decoded e n

-- | Decode @k@ steps of an encoding.
decode :: (forall x. Expr x -> Maybe (e x)) -> Int -> Expr n -> Maybe (Decoded e n)
decode f k t
  | k <= 0 = Just (Decoded TelescopeEmpty t)
  | AppE a (LamE binder rest) <- t = do
      payload <- f a
      Decoded tele body <- decode f (k - 1) rest
      Just (Decoded (TelescopeCons () payload binder tele) body)
  | otherwise = Nothing

-- | Refreshing a telescope with 'withRefreshedPattern', and its body with
-- the substitution it returns, gives an α-variant. The telescope is built
-- in a scope @n@ and sunk (through its encoding) into an extension @o@ of
-- @n@ that binds the names of its binders, so that every binder clashes.
transportLaw
  :: (Sinkable e, RelMonad Name e)
  => (forall x. e x -> Expr x) -> (forall x. Expr x -> Maybe (e x))
  -> (forall x. Ctx x -> Gen (Maybe (e x)))
  -> Property
transportLaw toE fromE genE =
  forAllShow genCase showCase $ \(TransportCase o k enc) -> withCtx o $ \scopeO ->
    case decode fromE k enc of
      Nothing -> counterexample "the encoding does not decode" False
      Just (Decoded tele body) ->
        withRefreshedPattern scopeO tele $ \extend tele' scope' ->
          let body' = substitute scope' (extend identitySubst) body
              refreshed = encode toE tele' body'
           in counterexample ("refreshed = " <> showAST sg refreshed) $
                alphaEqNameless nameBinderNames enc refreshed
  where
    genCase = do
      SomeCtx n <- genCtx
      PatIn tele <- genTelescope genE n
      body <- sized (genAST sg (extendCtx n tele) . min 8)
      let names = telescopeNames tele
      withCtx n $ \scopeN ->
        clashWith names scopeN $ \o ->
          let enc = sink (encode toE tele body)
           in pure (TransportCase (extendCtx n o) (length names) enc)

    -- Extend a scope by binders with the given names, which are fresh for it.
    clashWith :: Distinct n => [Int] -> Scope n -> (forall o. DExt n o => NameBinderList n o -> r) -> r
    clashWith [] _ cont = cont NameBinderListEmpty
    clashWith (x : xs) scope cont = withRefreshed scope (UnsafeName x) $ \b ->
      clashWith xs (extendScope b scope) $ \bs -> cont (NameBinderListCons b bs)

    showCase (TransportCase o _ enc) = unlines [ "o = " <> showCtx o, "encoding = " <> showAST sg enc ]

-- | An encoded telescope of @k@ steps in a scope @o@.
data TransportCase where
  TransportCase :: Ctx o -> Int -> Expr o -> TransportCase

spec :: Spec
spec = do
  describe "Telescope () Name" $ do
    coSinkableSpec telescopeNames (genTelescope genNamePayload) extensionByCoercion
    law Holds "withRefreshedPattern transports payloads along refreshed binders" $
      transportLaw Var (\case Var x -> Just x; _ -> Nothing) genNamePayload
  describe "Telescope () Expr" $ do
    coSinkableSpec telescopeNames (genTelescope genTermPayload) extensionByCoercion
    law Holds "withRefreshedPattern transports payloads along refreshed binders" $
      transportLaw id Just genTermPayload