First, a few stylistic points:
- It's more common to write the
div
and mod
functions in infix, as x `mod` y == 0
, etc.
- A parenthesized argument to a function is never written
f(x)
. It's always with a separating space like f (x)
, so you want primes (x `div` factor)
.
- The infix operator
$
is pretty much always both preceded and followed by a space.
- Similarly, in the
[x..y]
syntax, the ..
is normally not surrounded by any space, though when it is, it is both preceded and followed by space, so either [1,3..roof]
or [1, 3 .. roof]
would be more common than [1,3.. roof]
.
and some advice on functions:
even x
is preferred to x `mod` 2 == 0
- For positive values,
1 + truncate x
can be replaced with ceiling x
. Technically, though, you don't need to check any numbers greater than the square root, so floor (sqrt $ fromIntegral x)
works in your case.
Also for positive values, the function pair quot/rem
is equivalent to div/mod
and the former can compile to faster code on x86_64 (e.g., quot
on Int
s can be a single machine instruction), so many Haskellers will get in the habit of using them whenever the substitution works. (Alternatively, this is a ridiculous micro-optimization here, so sticking with the div/mod
pair is fine if it looks more natural.)
However, it might be even better to introduce a predicate:
divides :: (Integral a) => a -> a -> Bool
divides d n = n `rem` d == 0
so you can write y `divides` x
in your list comprehension, and use the section (`divides` x)
in your filter
.
Finally, it's usual practice to provide type signatures that are more general than the ones you've provided. Certainly, generalizing primes
and isPrime
to all integral types would be usual:
isPrime :: (Integral a) => a -> Bool
primes :: (Integral a) => a -> [a]
On the other hand, isLengthLessThan
is probably fine the way it as, as it's usual to assume list lengths will fit in a 64-bit Int
, for obvious reasons.
Now, with respect to your questions:
Should isLengthLessThan
be in a where under isPrime
?
That's up to you. A utility function that's used in only one function is often included in a where
clause, though if the function seems "independently useful", it's sometimes kept at top-level. In this small module, keeping it at top level seems reasonable. In a bigger module, you might stick it in a where
clause to reduce clutter.
Rearranging the functions can also help. If you put primes
at the top and then put utility functions after the functions that use them (e.g., primes
, then isPrime
, then isLengthLessThan
), it can make it easier to follow the program's organization.
Is isLengthLessThan
's name too verbose?
I'd say so. Maybe shorterThan
? Note that the character count is probably less of an issue than the word count -- shorterThan
is simple to read and understand; isLengthLessThan
belongs in a Java program as a method in the ListLengthCheckerFactory
class. But also, see below for how to eliminate this function entirely.
What should I name my "primes" function?
I'd say factors
-- I mean, it's obviously going to be the prime factorization, because what other factors
function would make sense?
Are the function type specifications called that or declarations?
According to the Haskell 2010 Report, they're called "type signatures", and the corresponding function definitions f x = ...
are called "bindings". Both type signatures and bindings are examples of "declarations", so isPrime :: Int -> Bool
is a declaration, but so is isPrime x = ...
. However, in informal speech, if you asked "should this binding have a type declaration?", everyone would know what you meant.
Should I put the type specifications right before the function or should I put it at the start?
Almost universally in Haskell code, type signatures are placed immediately preceding the corresponding binding rather than collected together at the beginning of the module. While collecting them together probably seems like a nice way of documenting the "interface" at the top of the module, it has the drawback that someone stumbling across the definition may assume there's no type signature (as signatures are often optional) and so never see it, and what's more the standard Haddock documentation tool is set up to have the signature and definition of each binding in the same place.
Do specifications help speed or do they only help us by giving us better errors?
If the type signature matches what would be inferred by GHC if it was missing (which is often the most general possible type), then there is no effect on generated code. Moreover, GHC is designed to generate efficient code even when highly generalized signatures are used, by specializing and inlining functions aggressively. You could most likely drop your signatures, or use the signatures I suggested above with no effect on performance.
So, yes, type signatures are primarily useful for generating better error messages, occasionally for catching certain design errors, and -- when the code is finished -- for documenting the interface.
Do parenthesis make compilation faster or do they only muddle code up?
Any effect of parentheses on compilation speed will be miniscule, and it's standard Haskell style to omit them except where they are necessary.
Anyway, incorporating the advice I gave above, my version of your program currently looks like the following. Note that I turned on -Wall
, which reminded me that your program doesn't actually need the modules it imports, and let me know that there was a type defaulting going on in the sqrt
call that I thought should be made explicit. I also ran hlint
on it, which didn't have any hints for me.
{-# OPTIONS_GHC -Wall #-}
module MorePrimes where
factors :: (Integral a) => a -> [a]
factors x
| isPrime x = [x]
| otherwise = factor : factors (x `div` factor)
where factor = head $ filter (`divides` x) [2..]
isPrime :: (Integral a) => a -> Bool
isPrime x
| x == 2 = True
| even x = False
| otherwise = shorterThan 2 [y | y <- [1,3..roof], y `divides` x]
where roof = floor $ sqrt (fromIntegral x :: Double)
shorterThan :: Int -> [a] -> Bool
shorterThan 0 _ = False
shorterThan _ [] = True
shorterThan n (_:ys) = shorterThan (n-1) ys
divides :: (Integral a) => a -> a -> Bool
divides d n = n `rem` d == 0
Finally, some additional optimizations.
Your test in the third case of isPrime
can be simplified. Since 1
always divides x
, you can replace the test with:
shorterThan 1 [y | y <- [3,5..roof], y `divides` x]
which is equivalent to:
null [y | y <- [3,5..roof], y `divides` x]
which would more usually be written:
not $ any (`divides` x) [1,3..roof]
allowing you to drop the definition of shorterThan
entirely and use:
isPrime :: (Integral a) => a -> Bool
isPrime x
| x == 2 = True
| even x = False
| otherwise = not $ any (`divides` x) [3,5..roof]
where roof = floor $ sqrt (fromIntegral x :: Double)
The other problem is that factors
is doing extra work here -- it's using isPrime
to check if x
is prime using an algorithm that handles even numbers specially for efficiency, but then it turns around and does trial division by all numbers [2..]
for composites. Instead, you could replace isPrime
with a function to find the first prime factor of x
, like so:
firstPrime :: (Integral a) => a -> a
firstPrime x
| even x = 2
| otherwise = head [y | y <- [3,5..roof]++[x], y `divides` x]
where roof = floor $ sqrt (fromIntegral x :: Double)
This finds the first prime factor below roof
, but throws in x
itself as a last resort. (There's an unnecessary check here that x
divides itself, so we could improve this code to avoid that, but it might not be worth the trouble.)
Then, factors
can grab this first prime factor, and see if it was x
itself (in which case x
is prime and we're done) or else look for more factors. The complete code for this version looks like:
{-# OPTIONS_GHC -Wall #-}
module MorePrimes where
factors :: (Integral a) => a -> [a]
factors x = case firstPrime x of
factor | factor == x -> [x]
| otherwise -> factor : factors (x `div` factor)
firstPrime :: (Integral a) => a -> a
firstPrime x
| even x = 2
| otherwise = head [y | y <- [3,5..roof]++[x], y `divides` x]
where roof = floor $ sqrt (fromIntegral x :: Double)
divides :: (Integral a) => a -> a -> Bool
divides d n = n `rem` d == 0
Obviously, further optimization is possible, as illustrated by @Gurkenglas's answer.