packages feed

hegg-0.3.0.0: test/Lambda.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveTraversable #-}
module Lambda where

import Data.String

import Test.Tasty
import Test.Tasty.HUnit

import Data.Maybe

import qualified Data.IntMap as IM
import qualified Data.Set as S

import Control.Applicative ((<|>))

import Data.Eq.Deriving
import Data.Ord.Deriving
import Text.Show.Deriving

import Data.Equality.Graph
import Data.Equality.Extraction
import Data.Equality.Analysis
import Data.Equality.Saturation
import Data.Equality.Matching
import Data.Equality.Matching.Database as D
import Data.Equality.Graph.Lens

data Lambda a
    = Bool !Bool
    | Num !Int
    | Symbol !String
    | Use a
    | Subst a a a

    | Add a a
    | Eq a a
    | If a a a

    | App a a
    | Lam a a
    | Let a a a
    | LFix a a
    deriving ( Eq, Ord, Functor
             , Foldable, Traversable
             )

deriveEq1 ''Lambda
deriveOrd1 ''Lambda
deriveShow1 ''Lambda

evalL :: Lambda (Maybe (Lambda ())) -> Maybe (Lambda ())
evalL = \case
    Bool n -> Just (Bool n)
    Num n  -> Just (Num n)
    Add a b -> do
        a' <- a >>= num
        b' <- b >>= num
        return (Num $ a' + b')
    Eq  a b -> do
        a' <- a
        b' <- b
        return (Bool $ a' == b')
    _ -> Nothing
  where
    num :: Lambda () -> Maybe Int
    num = \case
        Num i -> Just i
        _ -> Nothing

type FreeVars = S.Set String
-- the lambda evaluator analysis is a combined analysis of the free variable analysis and the constant folding analysis
type LA = (FreeVars, Maybe (Lambda ()))

-- Constant folding for lambda evaluator
instance Analysis (Maybe (Lambda ())) Lambda where
  makeA = evalL
  joinA = (<|>)
  modifyA c = case c^._data of
                Nothing -> (c, [])
                Just v  -> (c, [f v])
                  where
                    f = \case
                      Bool b -> Fix $ Bool b
                      Num i  -> Fix $ Num i
                      _ -> error "impossible, lambda () can't construct this"
                      

-- Free variable analysis for lambda
instance Analysis FreeVars Lambda where
  makeA = \case
    Use x -> x
    Let v a b -> (b S.\\ v) <> a
    Lam v a -> a S.\\ v
    LFix v a -> a S.\\ v
    Bool _ -> mempty
    Num _  -> mempty
    Add a b -> a <> b
    Eq a b -> a <> b
    App a b -> a <> b
    If a b c -> a <> b <> c
    Symbol x -> S.singleton x
    Subst a b c -> b <> a <> c

  joinA = (<>)


instance Language Lambda

instance Num (Fix Lambda) where
    fromInteger = Fix . Num . fromInteger
    (+) a b = Fix $ Add a b
    (-) = error "todo..."
    (*) = error "todo..."
    abs = error "todo..."
    signum = error "todo..."

unsafeGetSubst :: Pattern Lambda -> D.Subst -> ClassId
unsafeGetSubst (NonVariablePattern _) _ = error "unsafeGetSubst: NonVariablePattern; expecting VariablePattern"
unsafeGetSubst (VariablePattern v) subst = case IM.lookup v subst of
      Nothing -> error "Searching for non existent bound var in conditional"
      Just class_id -> class_id

isConst :: Pattern Lambda -> RewriteCondition LA Lambda
isConst v subst egr = isJust $ snd $ egr^._class (unsafeGetSubst v subst)._data

isNotSameVar :: Pattern Lambda -> Pattern Lambda -> RewriteCondition LA Lambda
isNotSameVar v1 v2 subst egr = find (unsafeGetSubst v1 subst) egr /= find (unsafeGetSubst v2 subst) egr

rules :: [Rewrite LA Lambda]
rules =
    [ ifP trP "x" "y" := "x"
    , ifP flP "x" "y" := "y"
    -- , ifP (pat $ Eq (pat $ Use "x") "e") "then" "else" := "else" :| conditionEqual (pat $ Let "x" "e" "then") (pat $ Let "x" "e" "else")

    , pat (Add "x" "y") := pat (Add "y" "x")
    , pat (Add (pat $ Add "x" "y") "z") := pat (Add "x" $ pat $ Add "y" "z")
    , pat (Eq "x" "y") := pat (Eq "y" "x")

    -- substitution introduction
    , pat (LFix "v" "e") := pat (Let "v" (pat $ LFix "v" "e") "e")
    , pat (App (pat $ Lam "v" "body") "e") := pat (Let "v" "e" "body")

    -- substitution propagation
    , pat (Let "v" "e" (pat $ App "a" "b")) := pat (App (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))
    , pat (Let "v" "e" (pat $ Add "a" "b")) := pat (Add (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))
    , pat (Let "v" "e" (pat $ Eq "a" "b")) := pat (Eq (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b"))
    , pat (Let "v" "e" (pat $ If "a" "b" "c")) := pat (If (pat $ Let "v" "e" "a") (pat $ Let "v" "e" "b") (pat $ Let "v" "e" "c"))

    -- substitution elimination
    , pat (Let "v" "e" "c") := "c" :| isConst "c" -- let const
    , pat (Let "v1" "e" (pat $ Use "v1")) := "e" -- let var same
    , pat (Let "v1" "e" (pat $ Use "v2")) := "v2" :| isNotSameVar "v1" "v2" -- let var diff
    , pat (Let "v1" "e" (pat $ Lam "v1" "body")) := pat (Lam "v1" "body") -- let lam same
    ]

rewrite :: Fix Lambda -> Fix Lambda
rewrite e = fst $ equalitySaturation e rules depthCost

lambdaTests :: TestTree
lambdaTests = testGroup "Lambda"
    [ testCase "if tr" $
        rewrite (ifL tr 1 2) @?= 1

    , testCase "if fl" $
        rewrite (ifL fl 1 2) @?= 2

    , testCase "lambda_under" $
      -- \x -> 4 + ((\y -> y) 4) = \x -> 8
        rewrite (lam "x" (4 + app (lam "y" (var "y")) 4)) @?= lam "x" 8

    {-
       This test requires at least the ConditionEqual rewrite condition helper
       and possibly dynamic rewrites. It would also be better to improve
       rewrite conditions before continuing down this path.

       For the analysis patch, being able to define the analysis
       compositionally and without expressiveness problems is good enough.

    , testCase "lambda_compose_many" $
        rewrite (Fix (Let "compose" (lam "f" (lam "g" (lam "x" (app (var "f") (app (var "g") (var "x"))))))
                          (Fix $ Let "add1" (lam "y" (Fix $ Add (var "y") 1)) (app (app (var "compose") (var "add1"))
                                                                                   (app (app (var "compose") (var "add1"))
                                                                                        (app (app (var "compose") (var "add1"))
                                                                                             (var "add1"))))))) @?= lam "x" (Fix $ Add "x" 5)
                                                                                             -}
    ]

ifP :: Pattern Lambda -> Pattern Lambda -> Pattern Lambda -> Pattern Lambda
ifP a b c = pat (If a b c)
trP, flP :: Pattern Lambda
trP = pat (Bool True)
flP = pat (Bool False)

-- TODO: recursion-schemes extension in separate package
ifL :: Fix Lambda -> Fix Lambda -> Fix Lambda -> Fix Lambda
ifL a b c = Fix (If a b c)
tr, fl :: Fix Lambda
tr = Fix $ Bool True
fl = Fix $ Bool False
lam :: Fix Lambda -> Fix Lambda -> Fix Lambda
lam i = Fix . Lam i
var :: Fix Lambda -> Fix Lambda
var = Fix . Use
app :: Fix Lambda -> Fix Lambda -> Fix Lambda
app x y = Fix $ App x y

instance IsString (Fix Lambda) where
  fromString = Fix . Symbol