You were in the right direction, but not quite. To continue your line of thought we could write
goodPrimes = -- filter (\products, squares -> squares > products) (drop 1 primes)
map (\ (_,_,p) -> p) $
filter (\ (product, square, prime) -> square > product )
(zip3 products
squares
(drop 1 primes))
packaging the squares, the products, and the primes together into triples, filtering the triples, and then recovering the primes back. This is known as decorate-process-undecorate paradigm, or "Schwartzian transform" in the olden days of yore.
Or we could re-implement filter with a list comprehension, fusing all the definitions and simplifying the result to
goodPrimes2 =
[ prime | ((prod, sq), prime) <- products `zip` squares `zip` drop 1 primes
, sq > prod ]
=
[ prime | ((prod, sq), prime) <-
zipWith (*) primes (drop 2 primes)
`zip` zipWith (*) (drop 1 primes) (drop 1 primes)
`zip` drop 1 primes
, sq > prod ]
=
[ p1 | ((p0, p2), p1) <-
primes `zip` (drop 2 primes) `zip` drop 1 primes
, p1*p1 > p0*p2]
=
[ p1 | (p0:p1:p2:_) <- iterate tail primes -- or (tails primes)
, p1*p1 > p0*p2]
(p0:p1:p2:_) is a list which, in pseudocode, could be written as [p0, p1, p2, ...], and iterate tail (or tails) produces a list of progressive tails of its input list, here primes.
There's no reuse of products and squares, so no point in pre-calculating those lists.
Trying it out:
> take 20 primes
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71]
> take 10 goodPrimes2
[5,11,17,29,37,41,53,59,67,71]
It can be nice to play with code, to shake it until it settles into something simple and clear. The sequence could be continued as
=
[ p1 | (p0:p1:p2:_) <- iterate tail primes -- or (tails primes)
, p1*p1 > p0*p2]
=
concatMap (\ case (p0:p1:p2:_) -> [p1 | p1*p1 > p0*p2] )
(tails primes)
=
tails primes >>= (\ case (p0:p1:p2:_) -> [p1 | p1*p1 > p0*p2] )
Thus,
goodPrimes3 = foo primes
where
foo = tails >=> (\ case (p0:p1:p2:_) -> [p1 | p1*p1 > p0*p2] )
but these last variants which treat lists as a "monad" are probably not interesting for you right now.
Incidentally, the mapping on progressive tails is a primitive in Common Lisp, known as maplist, and the concat-mapping ("flatmapping" as it is known in other languages) on tails is also a primitive there, mapcon (so in Haskell, mapcon f = (tails >=> f)).