简体   繁体   中英

How to deal with types in Haskell

I've started learning Haskell and I've been reading "Learn You a Haskell for Great Good". I read up to halfway through the Modules chapter. A friend showed me codewars and I decided to put some of what I've learnt to the test.

I am trying to make a function which returns a Boolean of whether the given Integral is a power of 4. Here is the code.

module PowerOfFour where

isWhole n = fromIntegral (round n) == n

isPowerOf4 :: Integral n => n -> Bool
isPowerOf4 4 = True
isPowerOf4 x = if x < 4 then
    False
else
    if isWhole (x / 4) then
    isPowerOf4 (truncate (x / 4))
  else
    False

This is the error message I get.

/tmp/haskell11524-8-kke52v/PowerOfFour.hs:10:12:
    Could not deduce (RealFrac n) arising from a use of `isWhole'
    from the context (Integral n)
      bound by the type signature for
                 isPowerOf4 :: Integral n => n -> Bool
      at /tmp/haskell11524-8-kke52v/PowerOfFour.hs:5:15-37
    Possible fix:
      add (RealFrac n) to the context of
        the type signature for isPowerOf4 :: Integral n => n -> Bool
    In the expression: isWhole (x / 4)
    In the expression:
      if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False
    In the expression:
      if x < 4 then
          False
      else
          if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False

/tmp/haskell11524-8-kke52v/PowerOfFour.hs:10:23:
    Could not deduce (Fractional n) arising from a use of `/'
    from the context (Integral n)
      bound by the type signature for
                 isPowerOf4 :: Integral n => n -> Bool
      at /tmp/haskell11524-8-kke52v/PowerOfFour.hs:5:15-37
    Possible fix:
      add (Fractional n) to the context of
        the type signature for isPowerOf4 :: Integral n => n -> Bool
    In the first argument of `isWhole', namely `(x / 4)'
    In the expression: isWhole (x / 4)
    In the expression:
      if isWhole (x / 4) then isPowerOf4 (truncate (x / 4)) else False

What am I doing wrong?

I used fromIntegral in isWhole to fix a similar error and isWhole now works fine individually. However, I can't seem to get rid of these errors in isPowerOf4. I've tried the possible fixes GHC provides, but I probably wasn't doing it right.

I'd rather keep the type signature of the isPowerOf4 function, as that is provided by codewars, so I'm guessing it's a requirement.

In Haskell the / operator is for fractional types not integral ones. For integral types you should use div (you can use it infix by surrounding it in backticks). You can also use mod or rem to find the remainder, like % in typical languages (they are different for negative numbers)

isPowerOf4 :: Integral n => n -> Bool
isPowerOf4 4 = True
isPowerOf4 x = if x < 4
  then False
  else
    if x `mod` 4 == 0
      then isPowerOf4 (x `div` 4)
      else False

As @leftaroundabout explained / is floating point division and the results may be object to rounding error. Therefore your is isWhole function is not guarantied to work correctly in all cases.

A better approach would be to use integer division and modulo, as suggested by @Jubobs.

div is integer division it gives you the result of a division with out the remainder. That's the same as you expect from truncate (x / 4) . This should be rewritten as div x 4 , so it's not relying on floating point numbers.

For isWhole you can use modulo this is the rest of an integer division. (eg mod 5 4 == 1 ) If it is zero there is no remainder, the result is a whole number. Instead of isWhole (x / 4) you can use mod x 4 == 0 . Both div and mod work exact so there is not danger of rounding errors.

Haskell never, ever does implict type conversions. That may seem rather obdurate to most programmers, but actually it is one of the reasons why Haskell's type system works so great. Completely shunning conversions makes many things much simpler, and it's pretty prerequisite for full bidirectional type inference.

In your case, the problem is in x / 4 . A division, and you seem expect the result to be possibly noninteger. But it must be integer, as long as it has an integral type 1 ! So you need to explicitly convert to a fractional type 2 first. The simplest way is indeed the fromIntegral function you've already discovered:

   if isWhole $ fromIntegral x / 4 then ...

To make it clear: that's parsed as if isWhole ( (fromIntegral x)/4 ) then . At this point you may wonder: if I just said Haskell never implicitly converts stuff, then why is it fine to divide by the integer literal 4 ? The answer to that is, Haskell doesn't have integer literals! It only has various numeric literals . A whole-number literal doesn't have any particular type, it is polymorphic (ie, it requests from the context what type is expected here , and than behaves as that type – provided that the type is in the Num class).


1 C “solves” this problem by making integer division trucate the result... which leads to a load of problems, probably more than you'd get with implicit conversion.

2 “Some fractional type” means, in Haskell, it'll default to Double . That type is very fast, but it's also not entirely unproblematic because floating-point numbers are inherently inexact. I'm not sure right now if this would be a problem in your application; because of the == comparison it might well be. Basically, you need to specify the type of isWhole to prevent that, as isWhole :: Rational -> Bool . The Rational type represents noninteger numbers as exact fractions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM