How do I build a list with a dependently-typed length?

前端 未结 3 508
我在风中等你
我在风中等你 2020-12-06 05:29

Dipping my toe into the waters of dependent types, I had a crack at the canonical \"list with statically-typed length\" example.

{-# LANGUAGE DataKinds, GADT         


        
3条回答
  •  盖世英雄少女心
    2020-12-06 06:14

    Never throw anything away.

    If you're going to take the trouble to crank along a list to make a length-indexed list (known in the literature as a "vector"), you may as well remember its length.

    So, we have

    data Nat = Z | S Nat
    
    data Vec :: Nat -> * -> * where -- old habits die hard
      VNil :: Vec Z a
      VCons :: a -> Vec n a -> Vec (S n) a
    

    but we can also give a run time representation to static lengths. Richard Eisenberg's "Singletons" package will do this for you, but the basic idea is to give a type of run time representations for static numbers.

    data Natty :: Nat -> * where
      Zy :: Natty Z
      Sy :: Natty n -> Natty (S n)
    

    Crucially, if we have a value of type Natty n, then we can interrogate that value to find out what n is.

    Hasochists know that run time representability is often so boring that even a machine can manage it, so we hide it inside a type class

    class NATTY (n :: Nat) where
      natty :: Natty n
    
    instance NATTY Z where
      natty = Zy
    
    instance NATTY n => NATTY (S n) where
      natty = Sy natty
    

    Now we can give a slightly more informative existential treatment of the length you get from your lists.

    data LenList :: * -> * where
      LenList :: NATTY n => Vec n a -> LenList a
    
    lenList :: [a] -> LenList a
    lenList []        = LenList VNil
    lenList (x : xs)  = case lenList xs of LenList ys -> LenList (VCons x ys)
    

    You get the same code as the length-destroying version, but you can grab a run time representation of the length anytime you like, and you don't need to crawl along the vector to get it.

    Of course, if you want the length to be a Nat, it's still a pain that you instead have a Natty n for some n.

    It's a mistake to clutter one's pockets.

    Edit I thought I'd add a little, to address the "safe head" usage issue.

    First, let me add an unpacker for LenList which gives you the number in your hand.

    unLenList :: LenList a -> (forall n. Natty n -> Vec n a -> t) -> t
    unLenList (LenList xs) k = k natty xs
    

    And now suppose I define

    vhead :: Vec (S n) a -> a
    vhead (VCons a _) = a
    

    enforcing the safety property. If I have a run time representation of the length of a vector, I can look at it to see if vhead applies.

    headOrBust :: LenList a -> Maybe a
    headOrBust lla = unLenList lla $ \ n xs -> case n of
      Zy    -> Nothing
      Sy _  -> Just (vhead xs)
    

    So you look at one thing, and in doing so, learn about another.

提交回复
热议问题