Haskell is a statically typed, purely functional programming language known for its concise syntax and mathematical precision. It utilizes lazy evaluation, meaning it only calculates values when they are strictly needed, allowing for the creation of infinite data structures. This encourages a declarative style of coding, where you describe what something is rather than how to compute it step-by-step.

Just a picture with railroads from Bulgaria. Not exactly related to Haskell and functional programming.
The problem: Red and Black Shirts
Imagine you enter a store where red shirts cost 18 USD and black shirts cost 11 USD. For some reason you have exactly 1188 USD and you are willing to spend all of these in that store for these two items. The puzzle is to figure exactly how many of each shirt you can buy. Mathematically, this is a Linear Diophantine Equation:
18x + 11y = 1188
We need to find integer values for x (red shirts) and y (black shirts) and these should be whole numbers (Z). Since there are two variables and only one equation, there could be multiple solutions – or none.
The solution
To solve that with Haskell, we will build a model, a mathematical solver and finally a generator for all posible answers.
1. Modeling the Equation
First, we define a custom data type to represent the equation ax+by=c. We call it LDE2 (Linear Diophantine Equation with 2 variables). We also define a checkSolution function that takes a pair of numbers (x, y) and returns True if they satisfy the equation:
|
1 2 3 4 5 6 7 |
data LDE2 = LDE2 Integer Integer Integer deriving (Show, Eq) type Solution2 = (Integer, Integer) checkSolution :: Solution2 -> LDE2 -> Bool checkSolution (x, y) (LDE2 a b c) = a * x + b * y == c |
data LDE2: This holds our three constants (a,bandc). For that problem this will be18,11and1188.checkSolution: This performs the matha*x + b*y == cand returns a boolean.
2. Finding a Concrete Solution
To find the first answer without guessing (as we are developers, developers, developers), we use the Extended Euclidean Algorithm. This algorithm calculates the Greatest Common Divisor (GCD) of a and b, while finding the coefficients x' and y', such that:
a*x' + b*y' = GCD(a, b)
|
1 2 3 4 5 6 7 |
extendedEuclid :: Integer -> Integer -> (Integer, Integer, Integer) extendedEuclid a 0 = (a, 1, 0) extendedEuclid a b = let (d, x', y') = extendedEuclid b (a `mod` b) x = y' y = x' - (a `div` b) * y' in (d, x, y) |
That recursive function returns a tuple (d, x, y).
However, this only solves for the GCD (which is 1 for 18 and 11). We need to solve for our total cost, 1188. We write concreteSolution, to scale these coefficients up. If 1188 is divisible by the GCD, we simply multiply x' and y' by the scaling factor k.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
concreteSolution :: LDE2 -> Maybe Solution2 concreteSolution (LDE2 a b c ) = let (d, x', y') = extendedEuclid a b in if c `mod` d /= 0 then Nothing else let k = c `div` d -- scale factor x0 = x' * k y0 = y' * k in Just (x0, y0) |
Running this on our problem gives us Just ((-3564), 5940). It is mathematically correct, but practically buying negative shirt means selling and this is not what we are into. Enterig a shop with the idea to sell stuff is somehow out of the scope here.
3. The Infinite Solution Factory
This is where Haskell’s lazy evaluation shines. We know that solution to Diophantine equations appear at regular intervals. If (x0, y0) is a solution, then (x0+k(b/d), y0-k(a/d) is also a solution for any integer k. We define diophantine to generate an infinite list of all integer solutions.
|
1 2 3 4 5 6 7 8 9 10 |
diophantine :: LDE2 -> [(Integer, Integer)] diophantine (LDE2 a b c) = case concreteSolution (LDE2 a b c) of Nothing -> [] Just (x0, y0) -> let d = gcd a b stepX = b `div` d stepY = a `div` d ks = 0 : concat [[k, -k] | k <- [1..]] in [ (x0 + k * stepX, y0 - k * stepY) | k <- ks ] |
The magic happens in ks. The list comprehension [[k, -k]|k<-[1..]] generates [[1,-1], [2,-2], [3,-3]...]We flatten this with concat and prepend 0, creating an infinite list of integers [0, 1, -1, 2, -2, 3, -3 ...] that spirals outward from zero:

4. Filtering for Real World Answers
Finally, to solve our shirt puzzle, we take this infinite stream and verify which pairs are valid in the real world (non-negative, as we want to buy shirts and not to sell them):
|
1 2 3 |
isPositive (red, black) = red >= 0 && black >= 0 -- To run it in GHCi -- filter isPositive (diophantine shirtProblem) |
Haskell lazily checks the infinite list until if finds the valid range, returning exactly 7 solutions:

Somehow I got to press Ctrl+C, as it is filtering through endless list and it does not know when to stop. But who knows in general.
These are the only possible combinations of shirts you could have bought. The code is here – https://github.com/Vitosh/Haskell/blob/main/code/02_LDE.hs
Enjoy the code and enjoy the rest of your day! 🙂
