In this project, I set out to build my own version of the sequence function in Haskell — a function that computes the Cartesian product of a list of lists. While Haskell’s standard library already provides sequence for lists, implementing it yourself is a fantastic way to learn about recursion, indexing, and how list comprehensions work under the hood.

This project was inspired by the video “Produit cartésien des éléments dans une liste aux autres”, where I explored how to generate all combinations of elements across several lists manually.

1. The idea behind the algorithm

The goal is to generate every possible combination of elements such that:

[[x1, x2, ...], [y1, y2, ...], [z1, z2, ...]]

becomes something like:

[[x1, y1, z1], [x1, y1, z2], [x1, y2, z1], [x2, y1, z1], ...]

This is a full Cartesian product across multiple dimensions.

2. The full Haskell implementation

mySequence :: [[a]] -> [[a]]
mySequence xs = 
    let lxs = mySequencePrepareLength xs
        ids = mySequencePrepareIds xs
    in [mySequenceList xs lids | lids <- mySequenceIdsn lxs ids l]
    where l = length xs - 1

mySequenceList :: [[a]] -> [Int] -> [a]
mySequenceList _ [] = []
mySequenceList [] _ = []
mySequenceList (x:xs) (idx:ids) = (x !! idx):(mySequenceList xs ids)

mySequencePrepareLength :: [[a]] -> [Int]
mySequencePrepareLength [] = []
mySequencePrepareLength (x:xs) = (length x):(mySequencePrepareLength xs)

mySequencePrepareIds :: [[a]] -> [Int]
mySequencePrepareIds [_] = (-1):[]
mySequencePrepareIds (_:xs) = 0:(mySequencePrepareIds xs)

mySequenceIdsn :: [Int] -> [Int] -> Int -> [[Int]]
mySequenceIdsn lxs ids idx
    | idx == 0 = if val == (cmp - 1)
                 then []
                 else let newids = subMySequence 0 ids 0
                          newids2 = subMySequence2 (length lxs - 1) newids 0
                      in mySequenceIdsn lxs newids2 (length lxs - 1)
    | val < cmp - 1 = 
        let newids = subMySequence idx ids 0
        in  newids:(mySequenceIdsn lxs newids (length lxs - 1))
    | otherwise = 
        let newids = subMySequence3 idx ids 0
        in  mySequenceIdsn lxs newids (idx - 1)
    where val = (ids !! idx)
          cmp = (lxs !! idx)

subMySequence :: Int -> [Int] -> Int -> [Int]
subMySequence _ [] _ = []
subMySequence idx (x:xs) n = if idx /= n
                             then x:(subMySequence idx xs (n + 1))
                             else (x + 1):(subMySequence idx xs (n + 1))

subMySequence2 :: Int -> [Int] -> Int -> [Int]
subMySequence2 _ [] _ = []
subMySequence2 idx (x:xs) n = if idx /= n
                              then x:(subMySequence2 idx xs (n + 1))
                              else (-1):(subMySequence2 idx xs (n + 1))

subMySequence3 :: Int -> [Int] -> Int -> [Int]
subMySequence3 _ [] _ = []
subMySequence3 idx (x:xs) n = if idx /= n
                              then x:(subMySequence3 idx xs (n + 1))
                              else 0:(subMySequence3 idx xs (n + 1))

3. Step-by-step breakdown

The process can be understood as a multi-dimensional counter:

4. The recursive iteration logic

The most interesting part is mySequenceIdsn, which behaves like a nested loop generator — but without using explicit loops.

The three small helpers subMySequence, subMySequence2, and subMySequence3 handle different aspects of this index adjustment process.

5. Testing the function

Let’s test the function with a small example:

main = print (mySequence [[1,2], [3,4], [5,6]])

The expected output is:

[[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]

Which corresponds exactly to the full Cartesian product of the three input lists.

6. Why build it yourself?

While Haskell’s built-in sequence or mapM functions already handle this, implementing it yourself offers valuable insight:

It’s also a great exercise in recursion with state carried through immutable data: the “state” of our index list evolves through recursive calls, without mutating anything.

7. Wrap-up

This project demonstrates how recursive functions can emulate the behavior of imperative loops — systematically generating all combinations across multiple lists.

Through mySequence, we built a small but powerful algorithmic pattern:

The result is a pure, mathematical way of building the Cartesian product, which deepens your understanding of Haskell’s approach to recursion and list construction.

In essence, we reinvented the “nested for loops” — the Haskell way.



Comment


Not that much comments



Next
Before