tags:

views:

72

answers:

2

[Disclaimer] I am very new to Haskell (and any FPL for that matter), just started learning today by reading YAHT. So my code might look "funny". Any help to improve the coding style would be appreciated as well.

I was trying to write a function in Haskell that generates a list with series 1 to n for a given value of n, starting with +1 and toggling the sign first after 1 number, then 2, then 3 and so on.

e.g. series 16 should produce [1,-2,-3,4,5,6,-7,-8,-9,-10,11,12,13,14,15,-16] (1 positive, 2 negative, 3 positive, 4 negative, ...).

I found that the sign changes after each triangular number, which equals the sum of first few natural numbers.

So I wrote this code:

module Test
where

--It accepts n and k, prints numbers 1 to n, starting with +1 and toggling their sign after each triangular number
series 0 = []
series n =
    if isTriangular n
        then series (getPrevTri n (n-1)) ++ getSeries (odd (n + (getPrevTri n (n-1)))) ((getPrevTri n (n-1)) + 1) (n - (getPrevTri n (n-1)))
        else series (getPrevTri n (n-1)) ++ getSeries (odd ((getNextTri n (n+1)) + (getPrevTri n (n-1)))) ((getPrevTri n (n-1)) + 1) (n - (getPrevTri n (n-1)))
            --The sign is negative for those numbers which follow an odd triangular number AND the triangular number previous to it is even
            --OR an even number AND the triangular number previous to it is odd.

getSeries sign start 0 = []
getSeries sign start n = 
    if sign == True
        then [start] ++ getSeries True (start+1) (n-1)
        else [-start] ++ getSeries False (start+1) (n-1)

--Checks whether n is a triangular number or not        
isTriangular 0 = False
isTriangular n =
    checkSum n 1

--Checks whether n is equal to sum of first few natural numbers, starting from k
checkSum n 0 = False
checkSum n k =
    if n == (k * k + k)/ 2
        then True
        else if n > (k * k + k)/ 2
            then checkSum n (k+1)
            else False

--Gets the triangular number just smaller than n, descending from k
getPrevTri 0 k = 0
getPrevTri n k =
    if k <= n
        then if isTriangular k
            then truncate k
            else getPrevTri n (k-1)
        else 0

--Gets the triangular number just greater than n, starting from k
getNextTri 0 k = 1
getNextTri n k =
    if k >= n
        then if isTriangular k
            then truncate k
            else getNextTri n (k+1)
        else 0

I had to add a call to "truncate" in "getPrevTri" and "gerNextTri" since it started producing fractional numbers. But still I'm getting this error:

*Test> :load "Test.hs"
[1 of 1] Compiling Test             ( Test.hs, interpreted )
Ok, modules loaded: Test.
*Test> series 16


<interactive>:1:0:
    Ambiguous type variable `t' in the constraints:
      `Integral t' arising from a use of `series' at <interactive>:1:0-8
      `RealFrac t' arising from a use of `series' at <interactive>:1:0-8
    Probable fix: add a type signature that fixes these type variable(s)
*Test> 

Could someone explain what is the source of this error?

And what does surprise me is that when I tried to debug this code, I modified it to http://pastebin.ca/1932564 which produced the similar error.

And then to http://pastebin.ca/1932556 and it surprisingly caused no error.

(Please find the output at the end of the respective posts.)

What I infer from it is that a call to

isTriangular n

causes a type error in

odd n

How is it possible when Haskell is a "pure" FPL and in which functions do not have any side effects?

I used GHCi, version 6.12.3 for these codes, on a Windows 7 x64 machine.

+4  A: 

There is no numerical type which implements both Integral (forced by odd) and RealFrac (forced by (/)). (These are typeclasses, if you don't know what I'm talking about, just wait until your tutorial shows about this)

You may replace either / by div or do an explicit cast via fromIntegral or similar. You may also do something like x/2 == 1 instead of odd x.

Edit: In your second pastebin file, you did the conversions via truncate, which is also possible.

Haskell strength is it's strong typing system, allowing you to do less programming errors but also involves the problem of having strange problem at obvious places. I would generally suggest you to provide type-informations at least at toplevel functions. (like myFunc :: Int -> Integer). This improves both readability and safety, as the compiler prompts you suddently if something went wrong. In ghci, you can easily find out about type informations via the :t command:

ghci> :t odd
odd :: (Integral a) => a -> Bool

Please notice, that you have to wrap parantheses around infix functions when using this:

ghci> :t ($)
($) :: (a -> b) -> a -> b
FUZxxl
Thanks FUZxxl. But I still don't understand why should a call isTriangular n "modify" (for lack of a better word) n that makes it unacceptable for odd.
Ninad
`odd` is implemented as `odd x = x mod 2 == 1`. The function `mod` is defined in the typeclass `Integral` (instances: `Integer` or `Int`), whereas `/` is defined in the typeclass `RealFrac` (instances `Rational', 'Float'). The problem is, that there's no type which is an instance of both `Integral` and `RealFrac`.
FUZxxl
I think I was not clear enough about my doubt.My doubt is, why#if isTriangular n then if odd n#should cause a type error and why#if True then if odd n#shouldn't. Why a call to isTriangular should affect the call odd n? Isn't the whole meaning of a "pure" FPL that a call with same arguments should produce same results always? No function is supposed to have side effects?
Ninad
`isTriangular` performs an ordinary division (`/`) onto it's argument, forcing it to be some kind of rational number (`RealFrac`). Because `isTriangular` will be called only in the first quote, the type error appears only there, because your second example won't force n to be a `RealFrac`.
FUZxxl
Oh. So it's something like the compiler infers the type of n in series by looking at *each place* where n is used within series. Am I right?
Ninad
@Ninad: basically, yes. More precisely, the type of series is `(RealFrac t, Integral t, Integral a) => t -> [a]`, which means that the type of the argument must match both constraints `RealFrac` (be a fractional number) and `Integral` (be an integral number). The `series` function is well-typed because there could be a type that matches both constraints. But when you write `series 16`, the compiler looks for a type that *unifies* both constraints (in addition to `Num` the constraint for integer constants), which doesn't exist (in fact, no type *satisfies* both constraints).
Gilles
+2  A: 

Because FUZxxI already gave you an answer you were looking for I will show you how your problem can be solved easier and a lot shorter. Just for information.

How would you solve your problem in your head? First, you have to 'generate' sequence containing [1,2,2,3,3,3,4,4,4,4 ... ] just to know where to change sign. That sequence, expressed in Haskell notation, would be -

numbers = concatMap (\x -> replicate x x) [1..]

Then you have to 'combine' each number of this sequence with corresponding number from sequence from 1 to n to give it proper sign - that would be -

series n = zipWith (\a b -> a*(-1)^(b `mod` 2 + 1)) [1..n] numbers

Well, and that's it. You have the solution. You can even express it as one-liner without using numbers variable. But i consider it less readable.

Matajon
@Matajon: Thanks a ton! That would help a lot!
Ninad
This is a very good example of how to think functionally. +1
FUZxxl