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
n1type written liken1 :: (Integral b, RealFrac a) => a -> b
but not liken1 :: (RealFrac a, Integral b) => a -> b
It’s the same withfloor :: (Integral b, RealFrac a) => a -> bAnswer: 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:
n1takes 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 -
n3have 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_yearbe 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_yearcould 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_yearto 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,monthandyearare probably not too big, it’s a good idea to make themInts. You also apparently missed a brace, I fixed that for you: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:
Intdoes not implement division notfloor. In Haskell, integral division uses a different function (divorquot), but you want floating division here. Sinceyearis pinned to be anInt, the subtrahend4 * floor(fromIntegral year / 4) + 2is also pinned to be anInt. 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 withfromIntegralbefore dividing (as you did before).fromIntegralhas the signature(Integral a, Num b) => a -> b. This means:fromIntegraltakes a variable of an integral type (such asIntorInteger) 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:This code compiles and runs fine (on my machine). Haskell has certain type-defaulting rules, causing the compiler to pick
Doubleas the type for all floating divisions.Actually, you can do better than that. How about using integer division instead of repeated float-point conversions?
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
fromEnumin front ofyear,monthandday. The functionfromEnum :: Enum a => a -> Intconverts any enumeration type to anInt. All available numeric types in Haskell (except the complex ones iirc) are member of the classEnum. It’s not a very good idea though, dince you usually haveIntarguments 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_yeara type-signature, its type defaults today_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2. This essentially means:day,monthandyearcan be of an arbitrary type that implements theIntegraltypeclass. The function returns a value of the same type asday. In this case,a,a1anda2are 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 haveThe variable
agets instaniated toInt16,a1becomesInt8anda2becomesInteger. So what’s the return-type in this case?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
That is: arguments may be of arbitrary
Integraltype, 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
unsafeCoercein the moduleUnsafe.Coerce, which has the typea -> 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) implementquotandremin hardware.divalso 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 implementdivbut notquotin hardware. The only one I can remember right now is mmix though)