How to specify a function operating on non-empty data structure with LiquidHaskell?

210 views Asked by At

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
1

There are 1 answers

3
zakyggaps On BEST ANSWER

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.