packages feed

falsify-0.4.0: src/Data/Falsify/ConcreteFun.hs

-- | Concrete functions
--
-- Intended for qualified import.
--
-- > import Data.Falsify.ConcreteFun ((:->)(..))
-- > import qualified Data.Falsify.ConcreteFun as ConcreteFun
module Data.Falsify.ConcreteFun (
    (:->)(..)
    -- * Construction
  , map
    -- * Application
  , apply
    -- * Rendering
  , render
  ) where

import Prelude hiding (map)

import Control.Monad
import Data.Bifunctor
import Data.Foldable (toList)
import Data.Kind
import Data.List (intercalate)
import Data.Maybe (fromMaybe, mapMaybe)

import Data.Falsify.Tree (Tree(..))

import qualified Data.Falsify.Tree as Tree

{-------------------------------------------------------------------------------
  Definition

  NOTE: @Nil@ is useful as a separate constructor, since it does not have an
  @Eq@ constraint.
-------------------------------------------------------------------------------}

-- | Concrete function
--
-- A concrete function is essentially a deep embedding: an explicit
-- representation of the ouput value of the function for every input value in
-- the function's domain.
--
-- Concrete functions are the central building block for generating random
-- functions, as proposed by Koen Claessen in \"Shrinking and showing
-- functions\", Haskell Symposium 2012
-- (<https://dl.acm.org/doi/10.1145/2430532.2364516>). Koen's key insight is
-- that we can start with an infinite concrete function that is defined on every
-- value in the input domain, but since in any given test that function is only
-- applied to a finite number of inputs, we can then shrink the concrete
-- function, throwing away entire chunks of the input domain, until we are left
-- with a finite representation which can be printed as part of a test output.
--
-- All of the cleverness for /generating/ concrete functions is about reducing
-- the explicitly represented /domain/ of the function. To shrink the /outputs/
-- of the function nothing special is needed, and we can just rely on Haskell's
-- laziness and the fact that @falsify@ is carefully constructed so that it can
-- generate infinite data types.
data (:->) :: Type -> Type -> Type where
  Nil   :: a :-> b
  Unit  :: a -> () :-> a
  Table :: Ord a => Tree (a, Maybe b) -> a :-> b
  Sum   :: (a :-> c) -> (b :-> c) -> (Either a b :-> c)
  Prod  :: (a :-> (b :-> c)) -> (a, b) :-> c
  Map   :: (b -> a) -> (a -> b) -> (a :-> c) -> (b :-> c)

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

instance Functor ((:->) a) where
  fmap _ Nil           = Nil
  fmap f (Unit x)      = Unit (f x)
  fmap f (Table xs)    = Table (fmap (second (fmap f)) xs)
  fmap f (Sum x y)     = Sum (fmap f x) (fmap f y)
  fmap f (Prod x)      = Prod (fmap (fmap f) x)
  fmap f (Map ab ba x) = Map ab ba (fmap f x)

-- | Change domain of concrete function
--
-- This is the basic building block for constructing new concrete functions.
map :: (b -> a) -> (a -> b) -> (a :-> c) -> b :-> c
map = Map

{-------------------------------------------------------------------------------
  Application
-------------------------------------------------------------------------------}

-- | Apply concrete function
apply :: (a :-> b) -> b -> (a -> b)
apply Nil         d _     = d
apply (Unit x)    _ _     = x
apply (Prod p)    d (x,y) = apply (fmap (\q -> apply q d y) p) d x
apply (Sum p q)   d exy   = either (apply p d) (apply q d) exy
apply (Table xys) d x     = fromMaybe d . join $ Tree.lookup x xys
apply (Map g _ p) d x     = apply p d (g x)

{-------------------------------------------------------------------------------
  Rendering
-------------------------------------------------------------------------------}

-- | Show concrete function
--
-- Only use this on finite functions!
render :: (Show a, Show b) => (a :-> b) -> b -> String
render p d = concat [
      "{"
    , intercalate ", " $ concat [
          [ show x ++ "->" ++ show c
          | (x,c) <- toTable p
          ]
        , ["_->" ++ show d]
        ]
    , "}"
    ]

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

-- | Generating a table from a concrete function
--
-- Used in 'render'.
toTable :: (a :-> b) -> [(a, b)]
toTable Nil         = []
toTable (Unit x)    = [((), x)]
toTable (Prod p)    = [ ((x,y),c) | (x,q) <- toTable p, (y,c) <- toTable q ]
toTable (Sum p q)   = [ (Left x, c) | (x,c) <- toTable p ]
                   ++ [ (Right y,c) | (y,c) <- toTable q ]
toTable (Table xys) = mapMaybe (\(a, b) -> (a,) <$> b) $ toList xys
toTable (Map _ h p) = [ (h x, c) | (x,c) <- toTable p ]