Search code examples
haskellcollatz

Haskell beginner: "No instance for...arising from..." error


My goal is to write a function that calculates the maximum Collatz number below a certain number 'n'. (It's a Project Euler question for those who are familiar.)

Some context: A Collatz number for a given integer is equal to the length of the Collatz sequence for that integer. A Collatz sequence for an integer is calculated as follows: the first number ("n0") in the sequence is that integer itself; if n0 is even, the next number in the sequence ("n1") is equal to n / 2; if n0 is odd, then n1 is equal to 3 * n0 + 1. We continue recursively extending the sequence until we arrive at 1, at which point the sequence is finished. For example, the collatz sequence for 5 is: {5, 16, 8, 4, 2, 1} (because 16 = 3 * 5 + 1, 8 = 16 / 2, 4 = 8 / 2,...)

I'm trying to write a function ("maxCollatzUnder") which, when passed an integer "m", returns the integer (less than or equal to m) which has the longest Collatz sequence (i.e., largest Collatz number). For example, maxCollatz 20 (i.e., what integer below (inclusive) 20 has the longest collage sequence?) should return 19 (the number 19 has a Collatz sequence of length 21: [19,58,29,88,44,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1]).

In the code below, the "collatz" and "collatzHelper" functions compile and run correctly. I'm having trouble with the "maxCollatzUnder" function. This function intends to (I) create a list of 2-tuples (x,y) for each integer x ranging from 1 to m (where m is the function argument) and where where y represents the Collatz number for integer x and then (II) look through the list for the highest Collatz number (i.e., y) and return its associated integer (i.e., x)

maxCollatzUnder n = foldl(\acc (i,j) -> if j > acc then i else acc) 0 
    (zip [1..n] ( map collatzLength [1..n]))
    where collatzLength n = length . collatz $ n

collatz n = map truncate $ collatzHelper n

collatzHelper 0 = [0]
collatzHelper 1 = [1]
collatzHelper n
    | (truncate n) `mod` 2 == 0 = [n] ++ collatzHelper (n/2)
    | otherwise = [n] ++ collatzHelper (3*n+1)

I get the following error when I (attempt to) compile.

*Main> :l PE14Collatz.hs
[1 of 1] Compiling Main             ( PE14Collatz.hs, interpreted )

PE14Collatz.hs:7:89:
    No instance for (RealFrac Int)
      arising from a use of `collatzLength'
    In the first argument of `map', namely `collatzLength'
    In the second argument of `zip', namely
      `(map collatzLength [1 .. n])'
    In the third argument of `foldl', namely
      `(zip [1 .. n] (map collatzLength [1 .. n]))'
Failed, modules loaded: none.

What's curious is that the code compiles and runs correctly if I change the "maxCollatzUnder" to the following code (see below). The only change is that, in the version below, the fold function returns "j" (i.e., the largest Collatz number) instead of "i" (i.e., the integer which generates the largest Collatz number).

maxCollatzUnder n = foldl(\acc (i,j) -> if j > acc then j else acc) 0 
    (zip [1..n] ( map collatzLength [1..n]))
    where collatzLength n = length . collatz $ n

Suggestions on a more efficient/elegant approach are welcome though I would still be interested in understanding the cause of this error.


Solution

  • Because of your use of truncate (a method of RealFrac) and / (a method of Fractional, a superclass of RealFrac), Haskell infers the following two type signatures for your last two functions:

    collatz :: (RealFrac a, Integral b) => a -> [b]
    collatzHelper :: RealFrac a => a -> [a]
    

    Haskell then tries to deduce the type of maxCollatzUnder, and its thought process goes like this:

    • "In collatzLength n = length . collatz $ n, we're passing n to collatz, so the argument to collatzLength must be a RealFrac."

    • "Therefore, in map collatzLength [1..n], [1..n] must be a list of RealFrac values."

    • "Therefore, the n in map collatzLength [1..n] must be a RealFrac type."

    • "Therefore, the n in zip [1..n] (which is the same n) must be a RealFrac type, and so [1..n] is a list of RealFracs."

    • "Therefore, the i in (\acc (i,j) -> if j > acc then i else acc) must be a RealFrac."

    • "Because the aforementioned lambda can return either i or acc, they must be the same type."

    • "Because j is being compared to acc, j must be the same type as acc — and thus the same type as i and a RealFrac."

    • "But wait— j is the return value from collatzLength, which is the return value of a call to length, and so it has to be an Int, but Int isn't in RealFrac!"

    • "ERROR! ERROR!"

    I have to go now (the Compiler Cabal doesn't like me giving away their secrets), but the shortest fix is to not use truncate and / and just use div for (floored) integer division.