8

I would like to define a data type in Haskell which is parametrized by an Int constant along the lines:

data Q (n :: Int) = Q n (Int,Int) -- non-working code

in order to allow me to define functions of the type:

addQ :: (Q n)->(Q n)->(Q n)
addQ (Q k (i1,j1)) (Q k (i2,j2))) = Q k (i1+i2,j1+j2)

The idea is that in this way I am able to restrict addition to Q's that have the same n. Intuitively, it feels that this should be possible, but so far all my (admittedly newbie) attempts have stranded on the rigors of GHC.

TylerH
  • 19,065
  • 49
  • 65
  • 86
BeMuSeD
  • 191
  • 1
  • 8
  • 1
    You can do that with `Nat`, see https://hackage.haskell.org/package/base-4.14.1.0/docs/GHC-TypeLits.html#t:Nat – Willem Van Onsem Mar 15 '21 at 15:52
  • 2
    You can't write `Q n (Int,Int)` for the constructor since `n` is not a type. What would that mean? You can use something like `data Q (n :: Nat) = Q (Int,Int)` with the right extensions. – chi Mar 15 '21 at 16:37

2 Answers2

9

As the comments say, this is possible with the DataKinds extension (technically it's possible to achieve something very similar without it, but it's very unergonomic).

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

import GHC.TypeLits (Nat)
data Q (n :: Nat) = Q Int

addQ :: forall (n :: Nat). Q n -> Q n -> Q n
addQ (Q x) (Q y) = Q (x + y)

let q1 = Q 1 :: Q 3
    q2 = Q 2 :: Q 3
in addQ q1 q2
-- => Q 3 :: Q 3

If you put the KnownNat constraint on n (also from GHC.TypeLits) you can also get the n as a regular term using the natVal function.

Cubic
  • 13,754
  • 4
  • 40
  • 81
  • How do I "put the `KnownNat` constraint on `n`"? – BeMuSeD Mar 16 '21 at 08:37
  • @BeMuSeD `KnownNat n =>`, it's just a constraint like any other. – Cubic Mar 16 '21 at 11:42
  • Just found what I needed here: https://stackoverflow.com/questions/59222632/create-a-type-for-each-integer-in-haskell/59228880#59228880 Sorry that I had not spotted this earlier! – BeMuSeD Mar 17 '21 at 17:57
1

Based on the hints and answer kindly supplied, I can give the answer I was looking for, including an example of how to unpack n from the parametrized data type.

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

import GHC.TypeLits (Nat,KnownNat,natVal)

data Q (n :: Nat) = Q (Int,Int)

instance (KnownNat n) => Show (Q n) where
    show q@(Q (i,j)) = "("++(show i)++","++(show j)++")::"++(show (natVal q))

addQ :: forall (n :: Nat). Q n -> Q n -> Q n
addQ (Q (i1,j1)) (Q (i2,j2)) = Q (i1+i2,j1+j2)
BeMuSeD
  • 191
  • 1
  • 8
  • Additionally, if your goal is only to "restrict the arguments to be the same", you don't need to make it a `Nat` - you can just leave it polymorphic within the bounds of the block in which you want it to be restricted to the same thing. – Georgi Lyubenov Mar 22 '21 at 09:24