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:
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
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
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 :)
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
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!!!
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
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 Int
s. 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.
Yes, your reasoning is about right.
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!
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With