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

Benjamin Hodgson

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

{-# LANGUAGE DataKinds, GADTs, KindSignatures #-}

-- a kind declaration
data Nat = Z | S Nat

data SafeList :: (Nat -> * -> *) where
    Nil :: SafeList Z a
    Cons :: a -> SafeList n a -> SafeList (S n) a

-- the type signature ensures that the input list has at least one element
safeHead :: SafeList (S n) a -> a
safeHead (Cons x xs) = x

This seems to work:

ghci> :t Cons 5 (Cons 3 Nil)
Cons 5 (Cons 3 Nil) :: Num a => SafeList ('S ('S 'Z)) a

ghci> safeHead (Cons 'x' (Cons 'c' Nil))
'x'

ghci> safeHead Nil
Couldn't match type 'Z with 'S n0
Expected type: SafeList ('S n0) a0
  Actual type: SafeList 'Z a0
In the first argument of `safeHead', namely `Nil'
In the expression: safeHead Nil
In an equation for `it': it = safeHead Nil

However, in order for this data-type to be actually useful, I should be able to build it from run-time data for which you don't know the length at compile time. My naïve attempt:

fromList :: [a] -> SafeList n a
fromList = foldr Cons Nil

This fails to compile, with the type error:

Couldn't match type 'Z with 'S n
Expected type: a -> SafeList n a -> SafeList n a
  Actual type: a -> SafeList n a -> SafeList ('S n) a
In the first argument of `foldr', namely `Cons'
In the expression: foldr Cons Nil
In an equation for `fromList': fromList = foldr Cons Nil

I understand why this is happening: the return type of Cons is different for each iteration of the fold - that's the whole point! But I can't see a way around it, probably because I've not read deeply enough into the subject. (I can't imagine all this effort is being put into a type system that is impossible to use in practice!)

So: How can I build this sort of dependently-typed data from 'normal' simply-typed data?


Following @luqui's advice I was able to make fromList compile:

data ASafeList a where
    ASafeList :: SafeList n a -> ASafeList a

fromList :: [a] -> ASafeList a
fromList = foldr f (ASafeList Nil)
    where f x (ASafeList xs) = ASafeList (Cons x xs)

Here's my attempt to unpack the ASafeList and use it:

getSafeHead :: [a] -> a
getSafeHead xs = case fromList xs of ASafeList ys -> safeHead ys

This causes another type error:

Couldn't match type `n' with 'S n0
  `n' is a rigid type variable bound by
      a pattern with constructor
        ASafeList :: forall a (n :: Nat). SafeList n a -> ASafeList a,
      in a case alternative
      at SafeList.hs:33:22
Expected type: SafeList ('S n0) a
  Actual type: SafeList n a
In the first argument of `safeHead', namely `ys'
In the expression: safeHead ys
In a case alternative: ASafeList ys -> safeHead ys

Again, intuitively it makes sense that this would fail to compile. I can call fromList with an empty list, so the compiler has no guarantee that I'll be able to call safeHead on the resulting SafeList. This lack of knowledge is roughly what the existential ASafeList captures.

Can this problem be solved? I feel like I might have walked down a logical dead-end.

pigworker

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.

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

How do I get the length of the first line in a multi line string?

来自分类Dev

How do I undefine %{dist} using rpmbuild to build SRPM?

来自分类Dev

libevent evhttp how do I get the body length or indeed any body data?

来自分类Dev

How do I give a list dynamically without hardcoding to MongoDB's $in?

来自分类Dev

how do I convert a double array in C to python list?

来自分类Dev

How do I effectively access list information in C++?

来自分类Dev

How do you do design-time, strongly-typed data binding?

来自分类Dev

How do I do a nested list (array) of schema references in json schema

来自分类Dev

How do I get a list of all the GitHub projects I've contributed to in the last year?

来自分类Dev

How to destroy elements (or shorten length) of list array in C#

来自分类Dev

How to return the length of a list as type Integer instead of Int in Haskell

来自分类Dev

How do You Build an RSS feed with Jade?

来自分类Dev

How do I select list of generic objects in abstract service using CriteriaQuery

来自分类Dev

How do I patch or modify a list inside of a function when running a unit test?

来自分类Dev

How do I open up a random url from a list of url's in a new time each time it is clicked?

来自分类Dev

How do I cast a 2D list to a void pointer and back

来自分类Dev

How Do I set drop down menu list open when I click on parent li Then open the sub li using javascript?

来自分类常见问题

How do I resolve ClassNotFoundException?

来自分类Dev

How do you build PoDoFo on Windows with libcrypto support?

来自分类Dev

Filter list items by length in Haskell

来自分类Dev

How do you sort list of strings in batch?

来自分类Dev

How can I separately build and develop libgomp (openMP runtime)?

来自分类Dev

How do i sort my listview alphabetically?

来自分类Dev

How do I parse a RestSharp response into a class?

来自分类Dev

How do I override a default keybinding in LightTable?

来自分类Dev

How do I modularize polyfills in Angular?

来自分类Dev

How do I declare a driver as global?

来自分类Dev

How do I combine lenses and functors?

来自分类Dev

How do I parse JSON in Racket?

Related 相关文章

  1. 1

    How do I get the length of the first line in a multi line string?

  2. 2

    How do I undefine %{dist} using rpmbuild to build SRPM?

  3. 3

    libevent evhttp how do I get the body length or indeed any body data?

  4. 4

    How do I give a list dynamically without hardcoding to MongoDB's $in?

  5. 5

    how do I convert a double array in C to python list?

  6. 6

    How do I effectively access list information in C++?

  7. 7

    How do you do design-time, strongly-typed data binding?

  8. 8

    How do I do a nested list (array) of schema references in json schema

  9. 9

    How do I get a list of all the GitHub projects I've contributed to in the last year?

  10. 10

    How to destroy elements (or shorten length) of list array in C#

  11. 11

    How to return the length of a list as type Integer instead of Int in Haskell

  12. 12

    How do You Build an RSS feed with Jade?

  13. 13

    How do I select list of generic objects in abstract service using CriteriaQuery

  14. 14

    How do I patch or modify a list inside of a function when running a unit test?

  15. 15

    How do I open up a random url from a list of url's in a new time each time it is clicked?

  16. 16

    How do I cast a 2D list to a void pointer and back

  17. 17

    How Do I set drop down menu list open when I click on parent li Then open the sub li using javascript?

  18. 18

    How do I resolve ClassNotFoundException?

  19. 19

    How do you build PoDoFo on Windows with libcrypto support?

  20. 20

    Filter list items by length in Haskell

  21. 21

    How do you sort list of strings in batch?

  22. 22

    How can I separately build and develop libgomp (openMP runtime)?

  23. 23

    How do i sort my listview alphabetically?

  24. 24

    How do I parse a RestSharp response into a class?

  25. 25

    How do I override a default keybinding in LightTable?

  26. 26

    How do I modularize polyfills in Angular?

  27. 27

    How do I declare a driver as global?

  28. 28

    How do I combine lenses and functors?

  29. 29

    How do I parse JSON in Racket?

热门标签

归档