好的。每当遇到类型问题时,最好的方法是向编译器提供显式类型注释。自从day
, month
and year
可能不太大,制作它们是个好主意Int
s。你显然还错过了一个大括号,我为你修复了这个问题:
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)
当我尝试编译此文件时,GHC 会输出以下相当长的错误消息:
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)
第二个错误是重要的错误,第一个错误更多的是后续错误。它本质上是说:Int
不执行除法floor
。在 Haskell 中,积分除法使用不同的函数(div
or quot
),但你想要浮动除法。自从year
被固定为Int
, 减数4 * floor(fromIntegral year / 4) + 2
也被固定为Int
。然后除以 3,但如前所述,不能使用浮动除法。让我们通过将整个术语“转换”为另一种类型来解决这个问题fromIntegral
在划分之前(就像你之前所做的那样)。
fromIntegral
有签名(Integral a, Num b) => a -> b
。这意味着:fromIntegral
接受一个整型变量(例如Int
or Integer
) 并返回任意数值类型的变量。
让我们尝试编译更新后的代码。类似的错误出现在定义中n2
,我也修复了它:
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)
这段代码编译并运行良好(在我的机器上)。 Haskell 有某些类型默认规则,导致编译器选择Double
作为所有浮动分区的类型。
事实上,你可以做得更好。使用整数除法而不是重复的浮点转换怎么样?
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
该算法应该始终产生与上面的浮点版本相同的结果。速度可能快十倍左右。反引号允许我使用一个函数(quot
)作为操作员。
关于你的第六点:是的,这很容易做到。只要放一个fromEnum
在...前面year
, month
and day
。功能fromEnum :: Enum a => a -> Int
将任何枚举类型转换为Int
。 Haskell 中所有可用的数字类型(除了复杂的 iirc)都是该类的成员Enum
。但这不是一个好主意,因为你通常有Int
参数和多余的函数调用会减慢程序的速度。最好显式转换,除非您的函数预计与许多不同类型一起使用。实际上,不必太担心微观优化。 ghc 有一个复杂且有些神秘的优化基础架构,可以使大多数程序运行得非常快。
修正案
后续1、2和3
是的,你的推理基本正确。
后续4
如果你不给出浮点变体day_of_year
类型签名,其类型默认为day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
。这本质上意味着:day
, month
and year
可以是实现以下功能的任意类型Integral
类型类。该函数返回与以下类型相同的值day
。在这种情况下,a
, a1
and a2
只是不同类型变量- 是的,Haskell 也有类型级别的变量(以及类型级别 [这是类型的类型],但那是另一个故事) - 可以满足任何类型。所以如果你有
day_of_year (2012 :: Int16) (5 :: Int8) (1 :: Integer)
变量a
被实例化为Int16
, a1
变成Int8
and a2
变成Integer
。那么这种情况下的返回类型是什么?
It's Integer
,看看类型签名!
后续5
事实上,你同时存在又不同时。使类型尽可能通用当然有其优点,但它会使类型检查器感到困惑,因为当没有显式类型注释的术语中涉及的类型太通用时,编译器可能会发现有不止一种可能键入一个术语。这可能会导致编译器通过一些标准化但有些不直观的规则来选择类型,或者它只是向您显示一个奇怪的错误。
如果您确实需要通用类型,请争取类似的东西
day_of_year :: Integral a => a -> a -> a -> a
也就是说:参数可能是任意的Integral
类型,但所有参数必须具有相同的类型。
永远记住哈斯克尔从不强制类型转换。当涉及(自动)转换时,几乎不可能完全推断类型。您只能手动投射。现在有些人可能会告诉您该功能unsafeCoerce
在模块中Unsafe.Coerce
,其类型为a -> b
,但你实际上不想知道。它可能不会做你想象的那样。
后续6
没有什么问题div
。当涉及负数时,差异开始出现。现代处理器(如 Intel、AMD 和 ARM 制造的处理器)实现quot
and rem
在硬件方面。div
也使用这些操作,但做了一些调整以获得不同的行为。当您并不真正依赖于负数的确切行为时,这会不必要地减慢计算速度。 (实际上有一些机器实现了div
但不是quot
在硬件方面。我现在唯一记得的是mmix /questions/tagged/mmix though)