I am trying to do the first exercise in the LiquidHaskell case study on Lazy Queues
module Main where
main :: IO ()
main = putStrLn "hello"
{-@ type Nat = {v:Int | 0 <= v} @-}
{-@ die :: {v:String | false} -> a @-}
die x = error x
{-@ measure realSize @-}
realSize :: [a] -> Int
realSize [] = 0
realSize (_:xs) = 1 + realSize xs
{-@ data SList a = SL {
size :: Nat
, elems :: {v:[a] | realSize v = size}
}
@-}
data SList a = SL { size :: Int, elems :: [a]}
{-@ type NEList a = {v:SList a | 0 < size v} @-}
{-@ hd :: NEList a -> a @-}
hd (SL _ (x:_)) = x
hd _ = die "empty SList"
okList = SL 1 ["cat"]
okHd = hd okList
okHd
fails:
app/Main.hs:30:13-18: Error: Liquid Type Mismatch
Inferred type
VV : {VV : (SList [Char]) | VV == Main.okList}
not a subtype of Required type
VV : {VV : (SList [Char]) | 0 < size VV}
In Context
VV : {VV : (SList [Char]) | VV == Main.okList}
Main.okList
: (SList [Char])
From the error message, I'm pretty sure I'm not giving enough information to LH for it to "know" that okList
is non-empty, but I can't figure out how to fix it.
I tried telling it explicitly with a post-condition(?):
{-@ okList :: NEList a @-}
okList = SL 1 ["cat"]
But this doesn't work:
app/Main.hs:29:5: Error: Specified Type Does Not Refine Haskell Type for Main.okList
Haskell: Main.SList [GHC.Types.Char]
Liquid : forall a. Main.SList a
Your refined type of okList
is not restricting the type. It restricted the size but loose the type from String
to a
.
Change
{-@ okList :: NEList a @-}
okList = SL 1 ["cat"]
to
{-@ okList :: NEList String @-}
okList = SL 1 ["cat"]
And it will work.
I have to admit that I don't know liquidhaskell very well so everything below may only be my wild guess:
The main reason you have to do this is you are defining okList
separately with the default constructor SL
. The refined type of SList
only promises that size v = realSize (elems v)
, the size of the list is inspected when you call the constructor, compared with the number literal and then discarded, not stored at (liquid) type level. So when you feed okList
to hd
, the only information available for reasoning are size v = realSize (elems v)
(from the refined data type) and size v >= 0
(size
is defined as a Nat
), hd
won't know if it's positive.
In hd okList
, liquidhaskell may not be able to evaluate the expression and by doing so substitute okList
with Sl 1 ["cat"]
and gain information about the size, so it can only make judgement depend on the refined type of okList
it inferred (in this case, SList String
). One evidence is hd $ SL 1 ["cat"]
will work with no refined type.