liquidhaskell-0.8.0.2: tests/pos/ClojurVector.hs
{-
https://twitter.com/BrandonBloom/status/701261641971683328
https://github.com/clojure/clojure/blob/d5708425995e8c83157ad49007ec2f8f43d8eac8/src/jvm/clojure/lang/PersistentVector.java#L148-L164
-}
{-@ LIQUID "--no-termination" @-}
module PVec (height, arrayFor) where
import Language.Haskell.Liquid.Prelude (liquidAssume)
import qualified Data.Vector as V
import Data.Bits
-- | Generalized tree with branching 32. We "store" the height in each `Node`
-- only because it is useful in the specification (each sub-tree has the same height).
-- The height is _never_ computed and so can be eliminated at run-time.
data Tree a = Leaf a
| Node Int (V.Vector (Tree a))
-- | Specify "height" of a tree
{-@ measure height @-}
height :: Tree a -> Int
height (Leaf _) = 0
height (Node h ls) = 1 + h
-- | Specify tree must be "balanced", each node has 32 children
{-@ data Tree a = Leaf a
| Node { ht :: Nat
, kids :: VectorN (TreeH a ht) 32
}
@-}
-- | ListN is a list of a given size N
{-@ type VectorN a N = {v:V.Vector a | vlen v = N } @-}
-- | TreeH is a tree of given height H
{-@ type TreeH a H = {v:Tree a | height v = H} @-}
-- | Specify tree height is non-negative
{-@ using (Tree a) as {v:Tree a | 0 <= height v} @-}
-- | Nodes and Leaves are simply trees with non-zero and zero heights resp.
{-@ type NodeT a = {v:Tree a | height v > 0} @-}
{-@ type LeafT a = {v:Tree a | height v = 0} @-}
-- | Vector stores the height
data Vec a = Vec { vShift :: Int -- ^ height
, vTree :: Tree a -- ^ actual nodes
}
-- | Refined type relates height of the `vTree` with `vShift`
{-@ data Vec a = Vec { vShift :: Nat
, vTree :: TreeLevel a vShift
}
@-}
{-@ type TreeLevel a L = {v:Tree a | L = 5 * height v} @-}
--------------------------------------------------------------------------------
arrayFor :: Int -> Vec a -> Maybe a
arrayFor i (Vec l n) = loop l n
where
{-@ loop :: level:Int -> TreeLevel a level -> Maybe a @-}
loop :: Int -> Tree a -> Maybe a
loop level node
| level > 0 = let b = shift i (- level) `mask` 31 -- get child index
node' = getNode node b -- get child
level' = level - 5 -- next level
in
loop level' node'
| otherwise = Just (getValue node)
-- TODO: refine types of bit-ops, currently use an "assume"
{-@ mask :: x:Int -> y:Nat -> {v:Nat | v <= y}@-}
mask :: Int -> Int -> Int
mask x y = liquidAssume (0 <= r && r <= y) r
where
r = x .&. y
-- | These are the "cast" operations, except now proven safe.
{-@ getNode :: t:NodeT a -> {v:Nat | v <= 31} -> {v:Tree a | height v = height t - 1} @-}
getNode :: Tree a -> Int -> Tree a
getNode (Node _ ts) n = ts V.! n
getNode _ _ = impossible "provably safe"
{-@ getValue :: LeafT a -> a @-}
getValue :: Tree a -> a
getValue (Leaf x) = x
getValue _ = impossible "provably safe"
{-@ impossible :: {v:String | false} -> a @-}
impossible = error