4

I wrote some code to do ordinal arithmetic in Haskell and am now trying to use Liquid Haskell to verify certain properties. However, I'm having trouble "reflecting" recursive functions. I've isolated a problem in the "less than" function below:

-- (Ord a n b) = a^n + b
{-@ data Ordinal [size] = Ord { a :: Ordinal, n :: Nat,  b :: Ordinal }
                        | Zero {} @-}
data Ordinal = Ord Ordinal Integer Ordinal
             | Zero
             deriving (Eq, Show)

{-@ measure size @-}
{-@ size :: Ordinal -> Nat @-}
size :: Ordinal -> Integer
size Zero = 1
size (Ord a n b) = (size a) + 1 + (size b)

{-@ inline ordLT @-}
ordLT :: Ordinal -> Ordinal -> Bool
ordLT _ Zero = False
ordLT Zero _ = True
ordLT (Ord a0 n0 b0) (Ord a1 n1 b1) =
    (ordLT a0 a1) || 
    (a0 == a1 && n0 < n1) || 
    (a0 == a1 && n0 == n1 && ordLT b0 b1)

one = (Ord Zero 1 Zero)     -- 1
w   = (Ord one 1 Zero)      -- omega
main = print w              -- Ord (Ord Zero 1 Zero) 1 Zero

Executing liquid ordinals.hs with just the above gives the following error:

Error: Cyclic type alias definition for `Main.ordLT`
14 |     {-@ inline ordLT @-}
                     ^^^^^
The following alias definitions form a cycle:
* `Main.ordLT`

So what is the proper way to reflect recursive functions? I've read the liquid haskell tutorial but I can't figure out what its examples are doing differently.

Alex Varga
  • 1,752
  • 2
  • 14
  • 17
  • 3
    I guess you can't inline recursive functions. How would that work? – chi Nov 21 '18 at 19:35
  • I guess I'm not exactly sure what "inline" (or the other pragmas) do. Maybe there is a different one I should be using? – Alex Varga Nov 21 '18 at 21:07
  • 2
    The README seems to document this, suggests you might need to use `reflect` , but I'm afraid I haven't used LH much yet https://github.com/ucsd-progsys/liquidhaskell/blob/develop/README.md – jberryman Nov 21 '18 at 21:52
  • This is the best resource I've found on this: https://arxiv.org/pdf/1806.03541.pdf. Just make sure to use `NewProofCombinators` rather than `ProofCombinators`. – Alex Varga Nov 30 '18 at 17:40

1 Answers1

2

It's a little difficult to be sure what you want to be doing without more context, but inline does indeed not work on recursive functions: inline lets you use a function in a type by expanding it at compile time (before creating the verification condition sent to the solver), so it needs to be possible to replace every occurrence of ordLT a b by some specific logic formula (which is impossible, since it's recursive).

If you need to be able to use arbitrary Haskell functions in the logic, you can look into using Refinement Reflection. Your example compiles with {-@ reflect ordLT @-} and {-@ LIQUID "--exact-data-cons" @-}. However, the function symbols created by refinement reflection are not automatically fully interpreted in the logic. The nitty-gritty details are discussed in this paper, and more approachable examples/explanation are present in these slides and this blog post. The main point to remember is that the ordLT symbol created by reflection will initially be treated as a completely uninterpreted function in the logic: the only thing that LH knows about it is something like a0 == a1 ==> b0 == b1 ==> ordLT a0 b0 == ordLT a1 b1 (if you call it on identical inputs, the results are identical).

In order to do something useful with ordLT in the logic, you will need to call ordLT at the value-level somewhere in scope, which will reveal the value of that particular call, since the return type of ordLT (the value-level function) asserts something about the output of ordLT (the logic-level uninterpreted function) on those inputs. Examples are given in the slides linked above & the paper. I hope that's enough to get you started!

Lucy Maya Menon
  • 1,560
  • 8
  • 15
  • Thanks for you thorough answer Peter! I had figured out the need to use `reflect` but was still struggling to get LH to "understand" the function, and I believe those resources will help. – Alex Varga Nov 23 '18 at 11:22
  • Follow up: if I declare `type LT5 = {v:Ordinal | ordLT v (Ord Zero 5 Zero) }`, how can I actually declare something like `one :: LT5` without an error? Even if I prove `ordLT one (Ord Zero 5 Zero)`, LH still doesn't allow this. – Alex Varga Nov 23 '18 at 19:07
  • 1
    @AlexVarga You need to ensure that the proof of this fact is in scope when Liquid is typechecking the definition. You might want to look at some of the combinators in the `Language.Haskell.Liquid.ProofCombinators` module, which are designed for dealing with this kind of thing. e.g. something like `one = let one' = Ord Zero 1 Zero in castWithTheorem (ordLT one' $ Ord Zero 5 Zero) one'` should work in this case. – Lucy Maya Menon Nov 23 '18 at 21:13