0

I have a working (albeit inefficient) function to check if a number is prime in Python and I'd like to convert it to Haskell. Efficiency doesn't matter to me right now as I am focusing primarily on making a readable algorithm in Haskell that uses language features a beginner would understand.

I'm new to Haskell so I may have the wrong mindset about how to approach this. My basic algorithm is to check if a number is odd and above 2 and if so, check if it's divisible by any number before until 2.

Problem: I don't know how to set i equal to an increasing term in the range [2..n].

Question: Is there a way to iterate over a range with a variable and refer to it?


Python:

def is_prime(n):
    if n < 2: 
        return False # by definition, numbers smaller than 2 are not prime
    if n == 2: # edge case, 2 is the only even number that is prime
        return True
    if (n % 2) == 0:
        return False # if even, n is not prime (except 2)
    else:
        for i in range(2,n): #iterate from 2 to the input(n)-1 (since range() is non-inclusive of stop param)
            if (n % i == 0): #if n is divisible by a number that isn't 1 or itself...
                return False # ...it isn't prime
    return True 

Haskell (What I have so far):

isPrimeInner :: Int -> Bool
isPrimeInner n = map (n `rem` i == 0) [2..n] where i 
   -- I don't know how to set i equal to an increasing term 
   -- in the range [2..n]

isPrime :: Int -> Bool 
isPrime 2 = True -- edge case
isPrime n = if n < 2 
              then False 
            else if n `rem` 2 == 0 
              then False
            else isPrimeInner n
Prithvi Boinpally
  • 427
  • 1
  • 9
  • 23
  • *"I don't know how to set i equal to an increasing term in the range [2..n]"* You could use a [lambda expression](https://wiki.haskell.org/Anonymous_function). – Mark Seemann Oct 05 '20 at 07:19
  • So would I use that to modify i every time map performs one iteration? For example: [2..n]!!i where (\i -> i + 1)? – Prithvi Boinpally Oct 05 '20 at 07:23
  • 2
    ``map (\i -> n `rem` i == 0) [2..n-1]`` will produce a list of booleans. Some functions like `and` and `or` can consume that list and produce a single boolean. You might need `not` as well. (Alternatively, use `any` or `all` from the libraries.) – chi Oct 05 '20 at 07:28
  • @chi or it can be re-implemented with LC and `null` easily and clearly. :) – Will Ness Oct 05 '20 at 07:29
  • @chi yeah I just realized that was a mistake as well. What I really should be doing is checking each value in the range and returning False as soon as a divisor (other than 1) is found – Prithvi Boinpally Oct 05 '20 at 07:34
  • 1
    In Haskell, `and (some list)` will evaluate the list only until the first false. Don't be afraid to produce the full list of booleans apparently trying all divisors: Haskell will not actually try all of them but only as many as demanded by `and`. The net result is that as soon as the first divisor is found you exit the "loop", so to speak. – chi Oct 05 '20 at 07:40
  • in fact, thanks to Haskell's lazy evaluation, lists *are* loops, and data structures are like descriptions of computations to be done to populate them. – Will Ness Oct 05 '20 at 11:57
  • A Haskell list is a linked list, but behaves a lot like a generator in Python. – chepner Oct 05 '20 at 13:38
  • @chepner [with *pointers* and all](https://en.wikipedia.org/wiki/Linked_list)? of course not, you'll say, and I'll agree. in fact, nothing in its data type definition says anything about pointers or linking. :) an implementation could implement it as anything by her choosing (including arrays) as long as the semantics of access are preserved. the [relevant Report section](https://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-340003.7) (and the links there) does not give any complexity guarantees (inherent in the "linked list" concept) and never even mentions that word. (contd.) – Will Ness Oct 06 '20 at 13:08
  • the singly-linked-list data structure makes it very easy to *efficiently* insert an element at any list position, including its end; or remove it; it makes it easy to maintain a growing/shrinking circular buffer; etc. . nothing of the sorts is even remotely possible with Haskell's `[]` data type. – Will Ness Oct 06 '20 at 13:08
  • @WillNess My point was that `map f [2..n]` doesn't call `f` on any element of the list until and as you iterate over the list "returned" by `map`. Maybe the better Python analogy is that `map f [2..n]` is much more like Python `map(f, range(2,n+1))` than `[f(x) for x in list(range(2,n+1))]`. – chepner Oct 06 '20 at 14:10
  • @chepner I'm just always peeved by this "`[]` is linked list" thing. :) as for Python, I don't know Python, I just read it as a pseudocode. :) but I thought it had generator comprehensions, so maybe something like `[f(x) for x in range(2,n+1)]` is OK? (or is it with the `( )` parens?....). – Will Ness Oct 06 '20 at 14:21
  • In Python, `map(f, range(2, n+1))` is roughly equivalent to the generator expression `(f(x) for x in range(2, n+1))`, which lazily applies `f` to a value as you iterate. The list comprehension `[f(x) for x in range(2, n+1)]` *immediately* builds a list (which is really a dynamically allocated, resizable array) containing the results of each function call. – chepner Oct 06 '20 at 14:24

3 Answers3

1

Simplest done with List Comprehensions:

isPrime :: Int -> Bool
isPrime n = n > 1 && ( n == 2 ||
   null [ () | i <- [2..n-1], rem n i == 0] )
   --          ~~~~~~~~~~~~~

Here i is drawn from the range 2 .. n-1, i.e. it takes on the increasing values from 2 to n-1, one at a time.

When it divides n evenly without a remainder, a () value is produced as a member of the output list, which is then tested for being empty, with null. The code thus expresses the concept "there does not exist such i from 2 to n-1 that divides n evenly".

The i <- [2..n-1] part is known as a generator part of a list comprehension, and rem n i == 0 a test expression.

Testing:

> filter isPrime [0..545]
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,
137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,
431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541]
it :: [Int]

> length it
100

Thanks to Haskell's lazy evaluation, when given a composite argument n, isPrime fails as soon as possible, with the smallest divisor i of n.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Why is the type change to Integral necessary? – Prithvi Boinpally Oct 05 '20 at 07:28
  • no, it isn't, it just something GHCi derived for me. :) – Will Ness Oct 05 '20 at 07:29
  • Ah. I tried using Int but that causes an error. It's a requirement that the function takes an Int so is it possible to write the function in such a way that it takes an Int rather than an Integral? – Prithvi Boinpally Oct 05 '20 at 07:30
  • there is no error. I've updated the answer. it works with `Int` just as well. – Will Ness Oct 05 '20 at 07:31
  • Huh, that's strange. And you're right no error when I copy paste from here. I was getting this before: "* Expected kind `* -> Constraint', but `Int' has kind `*' * In the type signature: isPrime :: Int a => a -> Bool" – Prithvi Boinpally Oct 05 '20 at 07:33
  • 1
    right, that is an invalid signature. `Int` is a simple type, but `Int a` would be a "constraint", like `Integral a`. Only constraints go to the left of `=>`. You could use `isPrime :: a ~ Int => a -> Bool` but that is just superfluous. – Will Ness Oct 05 '20 at 07:44
  • Ah ok that makes sense. Last question, could you explain what the () does in the LC What exactly is a () value? – Prithvi Boinpally Oct 05 '20 at 08:37
  • 1
    the `()` is an unimportant value. We could use `1`, `True`, or anything else there too, since we only check whether the list is empty or not; so the value itself is unimportant. and in Haskell, the unimportant value is `()`. – Will Ness Oct 05 '20 at 11:54
1

There is no assignment in Haskell. Unfortunately you hit a topic that requires you to learn a new concept: Instead of focusing on a single value (x) you should focus on the complete range/list of values.

Let's assume n = 8. A Python programmer might iterate (x in i in range(2, n)), a Haskell programmer might convert the input list [2..n] into a result list and continue to solve the problem from there:

xs = [   2,     3,    4,     5, ...]  -- input [2..n]
ys = [True, False, True, False, ...]  -- the resulting list of the divisibility test

Therefore I propose to break the problem down and solve the two sub-problems:

  1. How to compute ys from xs?
  2. Assuming your have the list of divisibility tests (ys). How can you solve your original problem? In your case having any True value as an element is important. Fortunately there is a function any that can help you.
Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
  • I think I see what you're saying. For subproblem 1, I think I can use map to apply a test to every element of the list xs (the test being n `rem` i == 0). As mentioned above, the only problem with this is I don't know how to increment i after each element in xs is tested. For sub-problem 2, I want to return False as soon as even a single True appears in ys because that means there is a divisor other than 1 and n. – Prithvi Boinpally Oct 05 '20 at 07:39
  • 1
    That is correct on both accounts. 1. Your `i` problem will go away when you give it a try and look at some examples of `map`. 2. A function you might be looking for is [any](http://zvon.org/other/haskell/Outputprelude/any_f.html). – Micha Wiedenmann Oct 05 '20 at 07:44
  • Ah I see the "any" makes sense. So for map, I think I should be doing: `map(n rem i == 0) xs` but I'm still a little confused on how to get i to equal the current element in xs. – Prithvi Boinpally Oct 05 '20 at 07:51
  • Nevermind I figured it out. Using the "<-" operator, I can make i take on a value in a range. – Prithvi Boinpally Oct 05 '20 at 08:37
1

Instead of using a loop, try to convert it to a tail recursion.

isPrimeHelper :: Integer -> [Integer] -> Bool
isPrimeHelper p (i:is) 
    | i*i > p = True
    | p `rem` i == 0 = False
    | otherwise = isPrimeHelper p is

isPrime :: Integer -> Bool
isPrime p = isPrimeHelper p [2..p]

the isPrimeHelper is the recursion version of the loop and the i appear in it is just like the i in the loop, and you can keep track of it.

for the equivalent Python code:

def isPrimeHelper(p : int, xs : [int]):
  i = xs[0]
  if i*i >p:
    return True
  elif p % i == 0:
    return False
  else:
    return isPrimeHelper(p, xs[1:])
Meowcolm Law
  • 392
  • 4
  • 13
  • This is pretty elegant. But what does the "i*i > p" check for? – Prithvi Boinpally Oct 12 '20 at 10:12
  • 1
    @Prithvi Boinpally kind of a trick when calculating prime number. We only need to check factors that are small the square root of the number (as the factors are kind of symmetrical) eg. for 18 -> 1, 2, 3, 6, 9, 18; 2 and 9 pair up, 3 and 6 pair up so we only need to check 2 and 3 which is smaller then sqrt(18) – Meowcolm Law Oct 13 '20 at 02:17