Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell numbers and type system?

I have this piece of Javascript code:

N1 = Math.floor(275 * month / 9)
N2 = Math.floor((month + 9) / 12)
N3 = (1 + Math.floor((year - 4 * Math.floor(year / 4) + 2) / 3))
N = N1 - (N2 * N3) + day - 30
return N

I tried to port that into a Haskell. Like this:

day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor( month + 9 / 12)
    n3 =  1 +  floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)

It doesn't work :(
Here are my questions:

  1. Why is n1 type written like n1 :: (Integral b, RealFrac a) => a -> b
    but not like n1 :: (RealFrac a, Integral b) => a -> b
    It's the same with floor :: (Integral b, RealFrac a) => a -> b

    Answer: the order is unimportant at left side of =>
    ghci will generally try to keep the order the same as the order in the declaration
    but sometimes it defaults to abc ordering

  2. Is this statement correct: n1 takes Integral number and returns RealFrac.

    Answer: Yes. If we know that ordering is unimportant at left side of =>
    then we also know that (Integral b, RealFrac a) === (RealFrac a, Integral b)
    what only matters are types a -> b
    or in this case Integral -> RealFrac

  3. n3 have Monomorphism sickness. How can it be cured?
    I am more interested in big picture of than just making this f work. I have read about mono... but I have no idea where to put :: in this case :(

    Answer: No monomorphism here. Look at FUZxxl's answer :)

  4. Can day_of_year be like this: Integral -> Integral -> Integral -> Integral?
    Takes 3 Integrals and return Integral result.

    Answer: Yes it can! It can also be
    :: Integral a => a -> a -> a -> a
    :: Int -> Int -> Int -> -> Int
    :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2

  5. I suppose day_of_year could take only 3 Ints or 3 Integers. It could not take a mix like 2 Ints 1 integer. Right?

    FUZxxl: No, it can take a mix of different argument types! Look at Follow up 4!!!

  6. Is it possible to create day_of_year to take 3 Nums and return a Num?

    FUZxxl: Yes it is! Put a fromEnum in front of year, month and day

like image 316
CoR Avatar asked Jun 27 '12 17:06

CoR


1 Answers

Okay. Whenever you have type-problems, it's the best way to start by giving explicit type annotations to the compiler. Since day, month and year are probably not too big, it's a good idea to make them Ints. You also apparently missed a brace, I fixed that for you:

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor((month + 9) / 12)
    n3 =  1 +  floor((year - 4 * floor(fromIntegral year / 4) + 2) / 3)

When I try to compile this, GHC spits out this rather lengthy error message:

bar.hs:8:16:
    No instance for (RealFrac Int)
      arising from a use of `floor'
    Possible fix: add an instance declaration for (RealFrac Int)
    In the second argument of `(+)', namely
      `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the expression:
      1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)
    In an equation for `n3':
        n3 = 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)

bar.hs:8:68:
    No instance for (Fractional Int)
      arising from a use of `/'
    Possible fix: add an instance declaration for (Fractional Int)
    In the first argument of `floor', namely
      `((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the second argument of `(+)', namely
      `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)'
    In the expression:
      1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)

The second error is the important error, the first one is more a follow-up. It essentially says: Int does not implement division not floor. In Haskell, integral division uses a different function (div or quot), but you want floating division here. Since year is pinned to be an Int, the subtrahend 4 * floor(fromIntegral year / 4) + 2 is also pinned to be an Int. Then you divide by 3, but as said before, you can't use a floating division. Let's fix that by 'casting' the whole term to another type with fromIntegral before dividing (as you did before).

fromIntegral has the signature (Integral a, Num b) => a -> b. This means: fromIntegral takes a variable of an integral type (such as Int or Integer) and returns a variable of any numeric type.

Let's try to compile the updated code. A similar error appears in the defintion of n2, I fixed it as well:

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = floor(275 * fromIntegral month / 9)
    n2 = floor((fromIntegral month + 9) / 12)
    n3 =  1 +  floor(fromIntegral (year - 4 * floor(fromIntegral year / 4) + 2) / 3)

This code compiles and runs fine (on my machine). Haskell has certain type-defaulting rules, causing the compiler to pick Double as the type for all floating divisions.

Actually, you can do better than that. How about using integer division instead of repeated float-point conversions?

day_of_year :: Int -> Int -> Int -> Int
day_of_year year month day = n1 - (n2 * n3) + day - 30
  where
    n1 = 275 * month `quot` 9
    n2 = (month + 9) `quot` 12
    n3 = 1 + (year - 4 * (year `quot` 4) + 2) `quot` 3

This algorithm should always yield the same result as the floating point version above. It's just probably about ten times faster. The backticks allow me to use a function (quot) as an operator.

About your sixth point: Yes, it would be pretty easy to do that. Just put a fromEnum in front of year, month and day. The function fromEnum :: Enum a => a -> Int converts any enumeration type to an Int. All available numeric types in Haskell (except the complex ones iirc) are member of the class Enum. It's not a very good idea though, dince you usually have Int arguments and superfluous function calls can slow down your program. Better convert explicitly, except if your function is expected to be used with many different types. Actually, don't worry about micro-optimizations too much. ghc has a complicated and somewhat arcane optimization infrastructure that makes most programs blazing fast.

Amendment

Follow-up 1, 2 and 3

Yes, your reasoning is about right.

Follow-up 4

If you don't give the floating-point variant of day_of_year a type-signature, its type defaults to day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2. This essentially means: day, month and year can be of an arbitrary type that implements the Integral typeclass. The function returns a value of the same type as day. In this case, a, a1 and a2 are just different type variables - yes, Haskell also has variables on type level (and also on kind level [which is the type of a type], but that's another story) - that can be satisfied with any type. So if you have

day_of_year (2012 :: Int16) (5 :: Int8) (1 :: Integer)

The variable a gets instaniated to Int16, a1 becomes Int8 and a2 becomes Integer. So what's the return-type in this case?

It's Integer, have a look at the type-signature!

Follow-up 5

In fact, you are and aren't at the same time. Making the type as general as possible certainly has its advantages, but at the it confuses the typechecker, because When the types involved in a term without an explicit type-annotation are too general, the compiler may find out that there is more than one possible type for a term. This may either cause the compiler to pick a type by some standardized albeit somewhat unintuitive rules, or it simply greets you with a strange error.

If you really need a general type, strive for something like

day_of_year :: Integral a => a -> a -> a -> a

That is: arguments may be of arbitrary Integral type, but all arguments must have the same type.

Always remember that Haskell never casts types. It's almost impossible to infer types completely when there is (automatic) casting involved. You only cast manually. Some people might now tell you about the function unsafeCoerce in the module Unsafe.Coerce, which has the type a -> b, but you actually don't want to know. It probably doesn't do what you think it does.

Follow-up 6

There is nothing wrong with div. The difference starts to appear when negative numbers are involved. Modern processors (like those made by Intel, AMD and ARM) implement quot and rem in hardware. div also uses these operations but does some twiddling to get a different behavior. That unneccessarily slows down computations when you don't really depend on the exact behavior regarding negative numbers. (There are actually a few machines that implement div but not quot in hardware. The only one I can remember right now is mmix though)

like image 150
fuz Avatar answered Sep 19 '22 16:09

fuz