packages feed

liquidhaskell-0.9.0.2.1: tests/pos/WhyLH.hs

module WhyLH where

{-@ LIQUID "--ple" @-}
{-@ LIQUID "--exact-data-cons" @-}

-- This test contains the examples of the blogpost at
-- https://www.tweag.io/blog/2022-01-19-why-liquid-haskell/
--
import Language.Haskell.Liquid.ProofCombinators (pleUnfold)
import Prelude hiding (length, max)

{-@
type Nat = {i:Int | 0 <= i}

data UExp
  = UVar Nat
  | ULam Ty UExp
  | UApp { uapp1 :: UExp, uapp2 :: UExp }
 @-}
-- | Lambda expressions with types at the bindings.
data UExp
  = UVar Int
  | ULam Ty UExp
  | UApp { uapp1 :: UExp, uapp2 :: UExp }

-- | The types are the types of functions manipulating some opaque type @T@.
data Ty = T | TyFun Ty Ty
  deriving Eq

-- XXX: Using inline instead of reflect causes verification to fail
{-@ reflect max @-}
max :: Int -> Int -> Int
max a b = pleUnfold (if a > b then a else b)

-- XXX: using max causes verification to fail if not using
-- --ple-with-undecided-guards. Arguably, PLE should unfold max
-- since it is not doing pattern matching.
{-@
reflect freeVarBound
freeVarBound :: UExp -> Int
@-}
-- | Compute the lowest upper-bound of de Bruijn indices appearing
-- free in an expression.
freeVarBound :: UExp -> Int
freeVarBound (UVar v) = v + 1
freeVarBound (ULam _ body) = max (freeVarBound body - 1) 0
freeVarBound (UApp e1 e2) = max (freeVarBound e1) (freeVarBound e2)

{-@
type UExpN N = { e:UExp | freeVarBound e <= N }
type ClosedUExp = UExpN 0
@-}

{-@ exp0 :: ClosedUExp @-}
exp0 :: UExp
exp0 = ULam T (UVar 0)

{-@ exp1 :: UExpN 1 @-}
exp1 :: UExp
exp1 = UVar 0

{-@ exp2 :: ClosedUExp @-}
exp2 :: UExp
exp2 = ULam T (ULam T (UVar 0))

{-@ exp3 :: UExpN 1 @-}
exp3 :: UExp
exp3 = ULam T (ULam T (UVar 2))


{-@ reflect elemAt @-}
{-@ elemAt :: xs:[a] -> { i:Int | 0 <= i && i < length xs } -> a @-}
elemAt :: [a] -> Int -> a
elemAt (x:_) 0 = x
elemAt (_:xs) i = elemAt xs (i - 1)

{-@ reflect length @-}
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

{-@ reflect inferType @-}
{-@ inferType :: ctx:[Ty] -> UExpN (length ctx) -> Maybe Ty @-}
inferType :: [Ty] -> UExp -> Maybe Ty
inferType ctx (UVar i) = Just (elemAt ctx i)
inferType ctx (ULam t body) = case inferType (t:ctx) body of
  Just r -> Just (TyFun t r)
  Nothing -> Nothing
inferType ctx (UApp e0 e1) =
  pleUnfold  -- needed by uappArgT to avoid breaking the verification
             -- in all of these cases
    (case inferType ctx e0 of
      Just (TyFun a r) -> case inferType ctx e1 of
        Just t -> if a == t then Just r else Nothing
        Nothing -> Nothing
      _ -> Nothing
    )

{-@ type WellTypedExp CTX TY = { e:UExp | freeVarBound e <= length CTX && inferType CTX e == Just TY } @-}

{-@ reflect cons @-}
cons :: a -> [a] -> [a]
cons = (:)

{-@ exp4 :: WellTypedExp (cons T []) T @-}
exp4 :: UExp
exp4 = UVar 0

{-@ exp5 :: WellTypedExp [] (TyFun T T) @-}
exp5 :: UExp
exp5 = ULam T (UVar 0)

main :: IO ()
main = print ()

-- The following functions are an experiment on what LH can infer from
-- the arguments of a data constructor. Given a well-typed application
-- @UApp e0 e1@, can LH infer that @e0@ must have a function type and
-- that the type of @e1@ must match the argument type of @e0@.

{-@ uappArgT :: ctx:[Ty] -> e : { e:UExp | isUApp e && isJustTy (inferType ctx e) } -> { funTyM (inferType ctx (uapp2 e)) (inferType ctx e) == inferType ctx (uapp1 e) } @-}
uappArgT :: [Ty] -> UExp -> ()
uappArgT _ _ = ()

{-@ inline isUApp @-}
isUApp :: UExp -> Bool
isUApp (UApp _ _) = True
isUApp _ = False

-- Either inline or both reflect and pleUnfold are needed by `uappArgT`.
{-@ inline isJustTy @-}
isJustTy :: Maybe a -> Bool
isJustTy (Just _) = True
isJustTy _ = False

{-@ inline funTyM @-}
funTyM :: Maybe Ty -> Maybe Ty -> Maybe Ty
funTyM (Just a) (Just b) = Just (TyFun a b)
funTyM _ _ = Nothing