6

I searched Hackage and couldn't find anything like the following but it seems to be fairly simple and useful. Is there a library that contains sort of data type?

data HList c where
  (:-) :: c a => a -> HList c
  Nil :: HList c

All the HLists I found could have any type, and weren't constrained.

If there isn't I'll upload my own.

Alec
  • 31,829
  • 7
  • 67
  • 114
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • I haven't seen one either. I've worked on something like that a while ago, but I never had it in a form where I felt like it should be uploaded. [Also, you can run into some weird corners of the type system exploring this](https://github.com/roboguy13/existentialist/blob/master/src/Data/Existentialist.hs#L67). – David Young Sep 12 '17 at 01:34

3 Answers3

10

I'm not sure this data type is useful...

  • If you really want a to be existentially qualified, I think you should use regular lists. The more interesting data type here would be Exists, although I'm certain there are variants of it all over package Hackage already:

    data Exists c where
      Exists :: c a => a -> Exists c
    

    Then, your HList c is isomorphic to [Exists c] and you can still use all of the usual list based functions.

  • On the other hand, if you don't necessarily want a in the (:-) :: c a => a -> HList c to be existentially qualified (having it as such sort of defies the point of the HList), you should instead define the following:

    data HList (as :: [*]) where
      (:-) :: a -> HList as -> HList (a ': as)
      Nil :: HList '[]
    

    Then, if you want to require that all entries of the HList satisfy c, you can make a type class to witness the injection from HList as into [Exists c] whose instance resolution only works if all the types in the HList satisfy the constraint:

    class ForallC as c where
      asList :: HList as -> [Exists c]
    
    instance ForallC '[] c where
      asList Nil = []
    
    instance (c a, ForallC as c) => ForallC (a ': as) c where
      asList (x :- xs) = Exists x : asList xs
    
Alec
  • 31,829
  • 7
  • 67
  • 114
  • A related question, is "Exists" anywhere? I've uploaded "polydata" which is basically "Exists", but I might as well someone else's package if it already exists to save duplication https://hackage.haskell.org/package/polydata-0.1.0.0/docs/Data-Poly.html – Clinton Sep 12 '17 at 02:00
  • It's old but searching around there appears to be a package called [`exists`](http://hackage.haskell.org/package/exists-0.2/docs/Data-Exists.html)... – Alec Sep 12 '17 at 02:02
  • Oh hang on, Exists and Poly are different. – Clinton Sep 12 '17 at 02:07
  • @Alec To construct `Exists c` you need to find a single example `a` such that `c a`. To construct `Poly c` you must show that an `a` exists for all `a` such that `c a`. I think the terminology is `Exists` is "existentially quantified" and `Poly` is "universally quantified". – Cirdec Sep 12 '17 at 02:52
  • @Cirdec thanks! The haddock was tripping me up. Looking at the source code, I see what you mean. – Alec Sep 12 '17 at 02:57
  • I [asked about whether your `Exists` type is in a library somewhere on IRC some time ago](http://tunes.org/~nef/logs/haskell/17.01.25) (conversation starts around 21:34). At that time nobody knew of a place that offered it, but folks did suggest some [cute](http://lpaste.net/351658) [ideas](https://github.com/glguy/operations). – Daniel Wagner Sep 12 '17 at 03:12
  • 1
    Why not use a type family to build the constraint `All c as` (`All c '[] = (); All c (a ': as) = (c a, All c as)`) rather than converting from `Hlist as` to `[Exist c]` which makes you lose information about the `as`? – gallais Sep 12 '17 at 11:23
  • @gallais Just a matter of taste. The problem with doing that is how to use 'All' afterwards. – Alec Sep 12 '17 at 14:39
  • @gallais Note that `ForallC as c` also holds exactly when `All c as` holds too - its just also lets you _do_ something with that information. – Alec Sep 12 '17 at 15:10
  • I don't see why you wouldn't be able to do something with `All c as`. You'd access constraints just like you access values in an HList: using an index. – gallais Sep 12 '17 at 15:47
  • @gallais I'm not sure I see what you mean, but I wish I did because it sounds interesting. Do you have some example somewhere of how this works for `All` (or something similar)? – Alec Sep 12 '17 at 15:54
  • What about something like this? https://gist.github.com/gallais/48b6dba11b1fd57d4a7386cfe502ff94 – gallais Sep 12 '17 at 17:17
  • @gallais Thanks! I see what you mean now. :) – Alec Sep 12 '17 at 17:19
  • 1
    I would argue that a type family `All` makes it much *easier* to actually use the constraints in practice than using just a type class. There's then no reason to first compute some term-level form where dictionaries are attached to values. – kosmikus Sep 12 '17 at 19:48
  • Quick update: a [reddit post](https://www.reddit.com/r/haskell/comments/7056me/discovered_that_one_can_do_this_data_t_a_where_t/dn0iypo/) suggests the [exists](https://hackage.haskell.org/package/exists-0.2/docs/Data-Exists.html) package for your `Exists` type. – Daniel Wagner Sep 14 '17 at 22:48
  • @DanielWagner That package was already linked to in the second comment. :) – Alec Sep 14 '17 at 22:49
  • @Alec Yeesh, I must be blind! – Daniel Wagner Sep 15 '17 at 03:38
5

The generics-sop package offers this out of the box.

A heterogeneous list can be defined in generics-sop by using

data NP :: (k -> *) -> [k] -> * where
  Nil  :: NP f '[]
  (:*) :: f x -> NP f xs -> NP f (x ': xs)

and instantiating it to the identity type constructor I (from generics-sop) or Identity (from Data.Functor.Identity).

The library then offers the constraint All such that e.g.

All Show xs => NP I xs

is the type of a heterogeneous list where all contained types are in the Show class. Conceptually, All is a type family that computes the constraint for every element in a type-level list:

type family All (f :: k -> Constraint) (xs :: [k]) :: Constraint where
  All c '[]       = ()
  All c (x ': xs) = (c x, All c xs)

(Only that in the actual definition, All is additionally wrapped in a type class so that it can be partially applied.)

The library furthermore offers all sorts of functions that traverse and transform NPs given a common constraint.

kosmikus
  • 19,549
  • 3
  • 51
  • 66
3

What you really want is

data HKList :: (k -> *) -> [k] -> * where
  Nil  :: HKList f '[]
  (:*) :: f x -> HKList f xs -> HKList f (x ': xs)

Which you can use either as an ordinary heterogeneous list

type HList = HKList Identity

Or with extra information of some constant type e attached to each value (or other interesting functors)

HKList ((,) e)

Or with extra information captured in a dictionary

data Has c a where
    Has :: c a => a -> Has c a

type ConstrainedList c = HKList (Has c)

Or keep lists of only captured constraints

data Dict1 :: (k -> Constraint) -> k -> * where
  Dict1 :: c k => Dict1 c k

Which you can use to define the idea of all of a list of types satisfying a constraint

class All c xs where
  dicts :: HKList (Dict1 c) xs

instance All c '[] where
  dicts = Nil

instance (All c xs, c x) => All c (x ': xs) where
  dicts = Dict1 :* dicts

Or anything else you can do with a kind k -> *

You can freely convert between working with All c xs => HList xs and HKList (Has c) xs

zipHKList :: (forall k. f k -> g k -> h k) -> HKList f xs -> HKList g xs -> HKList h xs
zipHKList _ Nil Nil = Nil
zipHKList f (x :* xs) (y :* ys) = f x y :* zipHKList f xs ys

allToHas :: All c xs => HKList Identity xs -> HKList (Has c) xs
allToHas xs = zipHKList f dicts xs
  where
    f :: Dict1 c k -> Identity k -> Has c k
    f Dict1 (Identity x) = Has x

hasToAll :: HKList (Has c) xs -> Dict (All c xs)
hasToAll Nil = Dict
hasToAll (Has x :* xs) =
  case hasToAll xs of
    Dict -> Dict

full code

I've written this a few times before for various projects, but I didn't know it was in a library anywhere until Kosmikus pointed out that it's in generics-sop.

Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • As discussed in the other answers, I think you'd be somewhat better off with turning `All` into a type family and not explicitly computing the `dicts`, but just using them directly. For this purpose, `generics-sop` offers "constrained" versions of functions such as your `zipHKList` that can use and distribute an `All` constraint over the elements. – kosmikus Sep 12 '17 at 19:53