Great that you've found your problem. There's few more lessons you can take from this.
so you now have
coprime_with :: Integer -> [Integer] -> Bool
coprime_with a [] = True
coprime_with a (b:bs) = if ((coprime a b) == True) then
(coprime_with a bs)
else False
whenever you see if A then B else False, you can replace it with A && B. Do you see why?
whenever you see A == True, you can replace it with just A. Do you see why?
So now we have
coprime_with a (b:bs) = (coprime a b) && (coprime_with a bs)
The parameter a
doesn't ever change, but we pass it on and on to every recursive invocation, needlessly. On the other hand, we must have it, as it is what coprime_with
is all about. The solution is to introduce an internal function,
coprime_with a bs = g bs -- "g" is "coprime_with a"
where
g [] = True
g (b:bs) = coprime a b && g bs
(we can write it without the parentheses as functional application in Haskell has the highest priority).
this is known as worker/wrapper transform. It makes our code cleaner, and lets compiler to perform more optimizations, potentially. Our new g
is the worker, and coprime_with
is the wrapper as it wraps g
inside. g
has a different type, and coprime_with
takes care of feeding it the proper arguments (here just the second one), and transforming its results back into what expected (here just passing them along).
Next, we can give a new name, in Haskell, to whatever (sub-)expression we want. Here, we'll do
coprime_with a bs = g pred bs -- "pred" is for "predicate"
where
g pred [] = True
g pred (b:bs) = pred b && g pred bs
pred = coprime a
what's the point to this? we're back at passing an extra parameter on recursion, you ask? Correct! This is not an optimization, but just a re-write, aiming to get somewhere. Next, we can even substitute back the definition of pred
coprime_with a bs = g (coprime a) bs
where
g :: (a -> Bool) -> [a] -> Bool
g pred [] = True
g pred (b:bs) = pred b && g pred bs
and that's our destination. What's left is to realize that we've reinvented a wheel (one of many). You can find what it is by searching for g
's type on Hoogle and examining the sources for the few top matches there to see which one is our g
exactly. Hint: you already were suggested its use, in the comments.
Now we can see clearly why it is True
and not False
that must be returned in the empty list case. Our g
checks to see whether the predicate holds for all elements of the list. In other words, it must fail (meaning here, return False
) if some element in the list fails the predicate — and so, return True
otherwise! But there are no elements in the empty list, to fail the predicate.
(another explanation for this issue that I like basically says, because g (a++b) == g a && g b
and a == a ++ []
, it must hold g a == g (a++[]) == g a && g []
).