Type for representing a list with 0 to 5 values

Well, a recursive solution is certainly the normal and in fact nice thing in Haskell, but it's a bit tricky to limit the number of elements then. So, for a simple solution to the problem, first consider the stupid-but-working one given by bradm.

With the recursive solution, the trick is to pass a “counter” variable down the recursion, and then disable consing more elements when you reach the max allowed. This can be done nicely with a GADT:

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

import Data.Kind
import GHC.TypeLits

infixr 5 :#
data ListMax :: Nat -> Type -> Type where
  Nil :: ListMax n a
  (:#) :: a -> ListMax n a -> ListMax (n+1) a

deriving instance (Show a) => Show (ListMax n a)


*Main> 0:#1:#2:#Nil :: ListMax 5 Int
0 :# (1 :# (2 :# Nil))

*Main> 0:#1:#2:#3:#4:#5:#6:#Nil :: ListMax 5 Int

<interactive>:13:16: error:
    • Couldn't match type ‘1’ with ‘0’
      Expected type: ListMax 0 Int
        Actual type: ListMax (0 + 1) Int
    • In the second argument of ‘(:#)’, namely ‘5 :# 6 :# Nil’
      In the second argument of ‘(:#)’, namely ‘4 :# 5 :# 6 :# Nil’
      In the second argument of ‘(:#)’, namely ‘3 :# 4 :# 5 :# 6 :# Nil’

For the sake of completeness, let me add an "ugly" alternative approach, which is however rather basic.

Recall that Maybe a is a type whose values are of the form Nothing or Just x for some x :: a.

Hence, by reinterpreting the values above, we can regard Maybe a as a "restricted list type" where lists can have either zero or one element.

Now, (a, Maybe a) simply adds one more element, so it is a "list type" where lists can have one ((x1, Nothing)) or two ((x1, Just x2)) elements.

Therefore, Maybe (a, Maybe a) is a "list type" where lists can have zero (Nothing), one (Just (x1, Nothing)), or two ((Just (x1, Just x2)) elements.

You should now be able to understand how to proceed. Let me stress again that this is not a convenient solution to use, but it is (IMO) a nice exercise to understand it anyway.

Using some advanced features of Haskell, we can generalize the above using a type family:

type family List (n :: Nat) (a :: Type) :: Type where
    List 0 a = ()
    List n a = Maybe (a, List (n-1) a)

I won’t answer your exercise for you — for exercises, it’s better to figure out the answer yourself — but here’s a hint which should lead you to the answer: you can define a list with 0 to 2 elements as

data List a = None | One a | Two a a

Now, think about how can you extend this to five elements.