2

Thanks to the answer to this question I've defined a type like so:

data Chain = forall a. Integral a => Chain [[a]] [a] a a

I need to write a getter function for each field, or argument if you like. Here's my first attempt:

getSimplices (Chain simplices _ _ _) = simplices

But when I try to compile ghc gives the following error:

Chain.hs:10:40: error:
• Couldn't match expected type ‘t’ with actual type ‘[[a]]’
    because type variable ‘a’ would escape its scope
  This (rigid, skolem) type variable is bound by
    a pattern with constructor:
      Chain :: forall a. Integral a => [[a]] -> [a] -> a -> a -> Chain,
    in an equation for ‘getSimplices’
    at Chain.hs:10:15-35
• In the expression: simplices
  In an equation for ‘getSimplices’:
      getSimplices (Chain simplices _ _ _) = simplices
• Relevant bindings include
    simplices :: [[a]] (bound at Chain.hs:10:21)
    getSimplices :: Chain -> t (bound at Chain.hs:10:1)

I fixed it like this:

getSimplices (Chain simplices _ _ _) = map (map fromIntegral) simplices

Even if some kind of ghc magic prevents this from being obscenely slow for a getter, I think fixing something this way is just atrocious. Is there a better way to define getters for types like this?

Eben Kadile
  • 759
  • 4
  • 21
  • 5
    What are you trying to do exactly? The fact that you want to write a getter for an existentially quantified type suggests to me that you didn't really want an existential in the first place. – Benjamin Hodgson Oct 30 '17 at 21:45
  • What I want is a single type that takes any integral data type for its fields – Eben Kadile Oct 30 '17 at 22:49
  • and you've got it, but what type should your getters produce? a consumer (caller) expects a certain Integral type, not "any Integral" type. – Will Ness Oct 30 '17 at 22:57
  • 1
    @EbenCowley [Don’t try to put constraints on datatypes](https://stackoverflow.com/questions/12770278/typeclass-constraints-on-data-declarations) – Benjamin Hodgson Oct 30 '17 at 23:01
  • Hm alright I see the issue now – Eben Kadile Oct 30 '17 at 23:08
  • One thing you can do in a situation like this is use `RankNTypes` to write `withSimplices :: Chain -> (forall a. Integral a => [[a]] -> r) -> r; withSimplices (Chain ss _ _ _) k = k ss`. That is, within the function `k` you have access to the hidden type `a`, as long as 1. you use it in a fully polymorphic way (only depending on the fact that it’s `Integral`), and 2. the result type `r` doesn’t mention `a` (or else `a` would be used outside the scope of the `forall`). An existential isn’t what you want here, but this is a useful thing to know about when you do need an existential. – Jon Purdy Oct 31 '17 at 18:41

2 Answers2

8

The constructor of an existential type necessarily "forgets" about a:

Chain :: Integral a => [[a]] -> [[a]] -> a -> a -> Chain

Note how the resulting type no longer depends on a.

A consequence of this is that, when we pattern match on Chain, we can not make any assumption about what a is. After all we could have chosen anything when constructing. The only knowledge we have on a is that it must be an integral type. Hence, we can only access using the methods of the Integral type class.

The general rule is that, when we pattern match on Chain, we must return a value of a type which does not depend on a. A getter like

getter (Chain _ _ x _) = x

violate that rule. After all, what type would that have?

getter :: Chain -> ???

Surely not

getter :: Chain -> a

which would instead mean that we can extract any type we wish from Chain. This can not realistically work: we can't be free to choose what type to put in, and then be also free to choose what type to take out.

What we can do, however, is to exploit Integral:

getter :: Chain -> Integer
getter (Chain _ _ x _) = fromIntegral x

Integer does not depend on a, so it is OK.

chi
  • 111,837
  • 3
  • 133
  • 218
  • 1
    nice. also, if OP's `getSimplices` were possible, what about `map getSimplices` then? each `Chain` in that argument list could be built from *anything* as long as it's an `Integral`, but `map` must produce values of the *same* type for all of them. – Will Ness Oct 30 '17 at 22:41
7

What type do you suppose getSimplices should have? The "obvious" choice is

getSimplices :: Integral a => Chain -> [[a]]

But this doesn't work with your implementation, because the caller of getSimplices gets to choose the a, and there's no guarantee that the values stored in the Chain are of the same type that the caller wants, since you threw that information away. Consider:

let c = Chain [[1 :: Int]] [2] 3 4
in (getSimplices c) :: [[Integer]]

This is clearly allowed by your two type signatures which promise to work for any Integral type, but also clearly cannot work without some conversion: the Ints have to be turned into Integers somehow.

As the comments say, this is rather unusual. It would be much simpler to add a type parameter to Chain, so that it tracks the type you used to create it, and constrains its output to that type as well:

data Chain a = Chain [[a]] [a] a a

getSimplices :: Chain a -> [[a]]
getSimplices (Chain xs _ _ _) = xs
Will Ness
  • 70,110
  • 9
  • 98
  • 181
amalloy
  • 89,153
  • 8
  • 140
  • 205
  • it's common to expect, in other languages, an automatic conversion in such situations where the source and the target types are known (even if it weren't forgotten by `Chain`). But Haskell does no implicit conversions, ever. (IIRC). – Will Ness Oct 30 '17 at 22:54
  • Realistically doing this would be fine because this is a personal project and I know that `Chain` will never act on a non-integral type. But The point of asking this question was because I wanted to know how to do this with the extra safety of the `Integral` typeclass. – Eben Kadile Oct 30 '17 at 23:00
  • Due to the comment by Benjamin Hodgson I'm accepting this as the best solution – Eben Kadile Oct 30 '17 at 23:08