Is it possible to avoid record syntax when defining refined data types?
Is it ossible to avoid data refinement altogether, relying on smart constructors?
`OHeap` and `LHeap` are two refinements on `Heap`; how could they be changed so that they compose?
When is it appropriate to use a refinement which exposes some property (e.g. depth) in its type?
Why doesn't LH figure out that a `Leaf` has rank 0?
I want to figure this out mostly on my own, but I think that it's about time I stopped flailing and asked some questions, hopefully learning things in the process.
Hi Alex,I have an edited version of your file at:
Now all the specs are verified _except_ the call to `Branch` inside `merge`which currently in fact violates the specification.Let me try to answer your great questions (which have also pointed outsome nasty LH issues that we will fix!)1. You need not have signatures in measure definitions, i.e. it should just be:{-@ measure rank @-}not{-@ measure rank :: Heap a -> Int @-}Writing the former _should_ return an error message, but it doesn't,which is something we will fix!
2. When using Boolean measures, you need not write `Prop` i.e. instead of< {-@ type LHeap a = {v:Heap a | Prop (isLeftist v)} @-}< {-@ type OLHeap a X = {v:OHeap a X | Prop (isLeftist v)} @-}just write:> {-@ type LHeap a = {v:Heap a | (isLeftist v)} @-}> {-@ type OLHeap a X = {v:OHeap a X | (isLeftist v)} @-}Writing `Prop` _should_ return a nice error message butunfortunately it doesn't which is clearly a bug in LHthat we should fix!
3. You cannot use arbitrary Haskell code in measures, e.g.`min` in the definition of rank; they must be terms thatcan be "lifted" into the refinement logic (i.e. belong inthe logic.) Thus, I have defined a `min` and used it hereinstead.> {-@ inline min @-}> min :: Int -> Int -> Int> min x y = if x < y then x else yYou can then use such functions via via the {-@ inline min @-}pragma. (As described also in the AVL chapter.)
4. I think you missed a "1 +" in the rank for leaf (otherwise therank is always 0), so I did that:< Branch _ l r -> min (rank l) (rank r)---> Branch _ l r -> 1 + min (rank l) (rank r)Now turning to your questions:Is it possible to avoid record syntax when defining refined data types?
Is it ossible to avoid data refinement altogether, relying on smart constructors?5. Yes, in geneval you need not have such constructors, but inyour specific case, where the types of one field *depend on* another(here, l and r depend on elt) you have to refine the data type.For example, suppose you just want an ordered pair:{-@ data OP = OP {x :: Int, y :: {v : Int | x < v} } @-}you could imagine a smart constructor:op ::x:Int -> {v: Int | x < v} -> OPbut this is not enough when you *destruct* the pair e.g.via pattern matching:case p ofOP a b -> assert (a < b)In theory you could solve this problem using "smart destructors"instead of pattern matching, but it could get a bit tedious.
The other benefit of refining the data type is that you "declaratively"specify the invariants in exactly one place without having to worryabout "how" the values were created (e.g. what if you found some wayto build a tree without using a smart constructor?)
6.`OHeap` and `LHeap` are two refinements on `Heap`; how could they be changed so that they compose?In this case, I would just eliminate LHeap and write OLHeap the way you have.In principle we could imagine a mechanism like:{-@ type OLHeap a X = OHeap a X /\ LHeap a @-}but we haven't implemented such a thing yet.
7.When is it appropriate to use a refinement which exposes some property (e.g. depth) in its type?Hmm, thats a tricky one -- in general, I'd say whenever you need to reasonabout that property. (But perhaps I've misunderstood the question?)Why doesn't LH figure out that a `Leaf` has rank 0?Now it does!I want to figure this out mostly on my own, but I think that it's about time I stopped flailing and asked some questions, hopefully learning things in the process.Points 1-3 *should* have been reported as spec/syntax errors, whichwould probably have saved you a great deal of effort. Apologies for thatand thanks for pointing these out!Let me know if you need any clarifications!Best,Ranjit.
Did you mean "the latter"? i.e. {-@ measure rank :: Heap a -> Int @-} should be an error?
I also noticed that when I removed the type signature, LH complained about `min` being unbound (before I added the inlined version), but not while the type signature was there.I'm trying to figure out where I got the idea that measures needed signature... It must have been from a misreading of chapter 4 of the tutorial, where an example .spec file is given with a measure which has a signature; the first occurrence of {-@ measure ... @-} isn't until chapter 6, and I just didn't notice that it lacked a signature.
2. When using Boolean measures, you need not write `Prop` i.e. instead of
How is that different from TRUE and FALSE in chapter two of the tutorial?
3. You cannot use arbitrary Haskell code in measures, e.g.`min` in the definition of rank; they must be terms that
The AVL chapter uses the inline pragma, but it's explanation is a single footnote with a FIXME, so I wasn't quite sure when it was necessary (the fact that LH didn't object while I had the type signature in place didn't help).
I'm also a little fuzzy as to why `min` needs to be inlined; it makes sense for functions that aren't defined in the Haskell Prelude, but shouldn't the GHC/Classes.spec file have enough annotation that a user can just use `min`?
I'm afraid I can't imagine what such a "smart destructor" would look like; is there an example you could point me at? I personally would have appreciated an alternative presented in chapter 5 of the tutorial, when refined data types are introduced. It would have made them feel less like magic.
True, but the flip side of that is decreased reuse (possibly not in ways that matter, in the end); that is, I can't use the same Haskell data type for binary trees (no refinement), binary search trees (just ordering), and both AVL red-black trees (two different kinds of balancing).
| {-@ type OHeap a = Heap <{\elt v -> v < elt }> a @-} |
data Q = Q Int
{-@ data Q = Q {v:Nat | true} @-}
data Q = Q Int
{-@ data Q = Q Nat @-}
I think I may have run into something that I don't fully understand in that vein:{-@ inline refine @-}
refine (Left x) = x < 0
refine (Right x) = x > 0
data Bar = Bar
{-@ Bar :: {v:Bar | unboundSymbol v } @-}
newtype Foo = Foo {getFoo :: Either Int Int}
{-@ Foo :: {v:Either Int Int | refine v} -> {v:Foo | refine (getFoo v)} @-}
{-@ getFoo :: Foo -> {v:Either Int Int | refine v} @-}
newtype Q = Q {q :: Int}
{-@ data Q = Q {q :: {v:Nat | true}} @-}
In my heap code, as I understand it, LH is currently complaining about the `Branch` call in `merge` because it can't prove that `merged` is smaller than `x1`, and this is because the refinement of `merge` does not provide any connection between the output and the inputs; is this correct? One idea I had was to change the refinement type to {-@ merge :: OHeap t x -> OHeap t y -> OHeap t (min x y) @-}, which led to an interesting error message (in emacs):
Suspicious state from syntax checker haskell-stack-liquid: Checker haskell-stack-liquid returned non-zero exit code 1, but no errors from output: LiquidHaskell Copyright 2009-15 Regents of the University of California. All Rights Reserved.
liquid:
**** ERROR *********************************************************************
**** ERROR *********************************************************************
**** ERROR *********************************************************************
Unexpected expression parameter: min x y in /home/alex/Documents/Haskell/LiquidHaskellTest/src/flycheck_Heap.hs:62:40: Error: Malformed Type Alias Application
Type alias: OHeap
Defined at: /home/alex/Documents/Haskell/LiquidHaskellTest/src/flycheck_Heap.hs:21:10
Expects 2 arguments, but is given 2
**** ERROR *********************************************************************
**** ERROR *********************************************************************
**** ERROR *********************************************************************
Checker definition probably flawed.{-@ measure smaller @-}
smaller Leaf _ = True
smaller _ Leaf = False
smaller (Branch x1 _ _) (Branch x2 _ _) = x1 <= x2I don't think I understand why neither works, and I don't know how I should relate the output to the input. Would you give me a hint?
Thanks again for taking the time to answer my questions!