【问题标题】:Haskell numbers and type system?Haskell 数字和类型系统?
【发布时间】:2012-06-29 05:14:49
【问题描述】:

我有这段 Javascript 代码:

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

我试图将它移植到 Haskell 中。像这样:

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)

它不起作用:(
以下是我的问题:

  1. 为什么n1类型写成n1 :: (Integral b, RealFrac a) => a -> b
    但不像n1 :: (RealFrac a, Integral b) => a -> b
    floor :: (Integral b, RealFrac a) => a -> b也一样

    答案: =>
    左侧的顺序不重要 ghci 通常会尽量保持与声明中的顺序相同
    但有时它默认为 abc 排序

  2. 这个说法是否正确:n1 接受整数并返回 RealFrac。

    回答: 是的。如果我们知道 =>
    左侧的排序不重要 那么我们也知道 (Integral b, RealFrac a) === (RealFrac a, Integral b)
    唯一重要的是类型 a -> b
    或者在这种情况下 Integral -> RealFrac

  3. n3 患有单态病。怎么治好?
    我对大局更感兴趣,而不仅仅是让这个 f 工作。我已经阅读过有关单声道的信息......但我不知道在这种情况下将 :: 放在哪里:(

    答案:这里没有单态性。看看 FUZxxl 的回答 :)

  4. day_of_year可以这样:Integral -> Integral -> Integral -> Integral吗?
    取 3 个积分并返回 Integral 结果。

    答案:可以!也可以
    :: 积分 a => a -> a -> a -> a
    :: Int -> Int -> Int -> -> Int
    :: (积分 a, 积分 a2, 积分 a1) => a -> a1 -> a2 -> a2

  5. 我想day_of_year 可能只需要 3 个整数或 3 个整数。它不能像 2 Ints 1 integer 这样的混合。对吧?

    FUZxxl:不,它可以混合使用不同的参数类型!看后续4!!!

  6. 是否可以创建day_of_year 以获取 3 个 Num 并返回一个 Num?

    FUZxxl:是的!在年月日前面放一个fromEnum

【问题讨论】:

  • 为什么这被否决了?它是具体的、可回答的,并且 OP 已经表明他们已经解决了这个问题。

标签: haskell types numbers


【解决方案1】:

我有太多后续问题需要简单评论。

  1. n1 很明显。 fromIntegral 接受 month 并将其转换为 / 的某些类型。
    我在吗?

    是的。

  2. 但是在n2 我们可以假设
    fromIntegral(month + 9) === (fromIntegral month + 9)

    • 在第一种情况下,month9 被添加,然后转换为 / 的某种类型 这是因为+Num 中,所以每个数字都可以是+ 而无需强制转换。 而像1,2,3 这样的原始数字也是Num 类型。
    • 第二种情况有某种“延迟铸造”。 (fromIntegral month + 9) 具有 Num a => a 类型,但由于 /12 编译器将 month9 转换为与 / 兼容的某些类型。
      我做对了吗?

      !是的。

  3. FUZxxl,伙计,谢谢!
    我非常接近通过修改代码并随机放置fromIntegral来解决这个问题。
    但是让代码工作并不等于知道我们为什么要做某事!

    • Integral 不允许同时使用 floor/
    • n3,第二个 year 变量: 通过使用floor(fromIntegral year / 4),我意外地得到了floor 可以使用的结果。 并且这个表达式变得完整
      (year - 4 * floor(fromIntegral year / 4) + 2) 一个 Integral 类型类!
      因此,/3 和第一个 floor 无法正常工作。
      我的逻辑没问题吗?

      !是的。

  4. 您的打字有效:day_of_year :: Int -> Int -> Int -> Int
    我的打字也有效:day_of_year :: Integral a => a -> a -> a -> a
    自动输入:day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2
    这是什么意思?什么是a1、a2?为什么是 a、a1、a2?

    后续 4 中的精彩回答

  5. 我在这里尝试创建采用Integral 而不是特定IntInteger 的通用函数是否犯了错误?

    • 在 JavaScript 中,所有内容都是自动转换/动态输入/从类型 Number 中输入的。
    • 在 C++ 中有templates,因此泛型函数适用于多种类型。

      !看后续5

  6. 您为什么使用quot 而不是div
    昨天我完全尝试了你对 divghci 所做的事情奖励我:
    No instance for (Integral (Car -> Int)) arising from a use of div

    • 这是什么错误?
    • 在这种情况下 div 和 quot 有什么区别?

      !我不小心从函数定义中删除了 yearmonth
      ! day_of_year day = ...
      !并且出现了那个错误。

附言Google:没有找到与 haskell "(Integral (Car -> Int))" 相关的结果

连谷歌都找不到我的错别字 ;)))

【讨论】:

  • 不要在答案中提出问题。
  • 这是一般规则,我知道。那该怎么办?我不能把它们放在原来的问题中。太多了,也太不一样了。我真的无法在评论中正确放置和格式化它们。唯一的方法是编写 6 个单独的 cmets,每个问题一个。但是,所有格式都会丢失,并且很难阅读。
  • 好吧,如果您对 SO 上的单个帖子有太多问题,您可以考虑多研究一下教程、文档或教科书上的主题。当心,我不是说现在就是这种情况,我说的是一般情况:)祝你的问题好运,但是(对不起,我现在没有时间,截止日期像一群食肉动物一样接近)。
  • 我正在学习 :) 这就是为什么我主要要求进行现实检查。我认为通常不是 Haskell 中真正发生的事情;)
  • @CoR 抱歉,我最后一天离线。如果您的问题与原始主题相差太远,请考虑提出另一个问题(顺便说一句,这会给您带来更多声誉)。另外:不要忘记接受最佳答案。
【解决方案2】:

好的。每当您遇到类型问题时,最好的方法是向编译器提供显式类型注释。由于daymonthyear 可能不会太大,所以将它们设为Ints 是个好主意。你显然也错过了一个大括号,我为你修好了:

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: 没有 (RealFrac Int) 的实例 由使用“地板”引起 可能的修复:为 (RealFrac Int) 添加实例声明 在 `(+)' 的第二个参数中,即 `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)' 在表达式中: 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3) 在“n3”的等式中: n3 = 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3) bar.hs:8:68: 没有 (Fractional Int) 的实例 由使用 `/' 引起 可能的解决方法:为 (Fractional Int) 添加实例声明 在“floor”的第一个参数中,即 `((年 - 4 * floor (fromIntegral year / 4) + 2) / 3)' 在 `(+)' 的第二个参数中,即 `floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)' 在表达式中: 1 + floor ((year - 4 * floor (fromIntegral year / 4) + 2) / 3)

第二个错误是重要错误,第一个更多是后续。它本质上是说:Int 没有实现除法而不是floor。在 Haskell 中,整数除法使用不同的函数(divquot),但您需要在此处进行浮点除法。由于year 被固定为Int,因此减数4 * floor(fromIntegral year / 4) + 2 也被固定为Int。然后除以 3,但如前所述,不能使用浮动除法。让我们通过在除法之前用fromIntegral 将整个术语“转换”为另一种类型来解决这个问题(就像您之前所做的那样)。

fromIntegral 具有签名(Integral a, Num b) => a -> b。这意味着:fromIntegral 接受一个整数类型的变量(例如IntInteger)并返回一个任意数值类型的变量。

让我们尝试编译更新后的代码。 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) 作为运算符。

关于您的第六点:是的,这样做很容易。只需在yearmonthday 前面加上fromEnum。函数fromEnum :: Enum a => a -> Int 将任何枚举类型转换为Int。 Haskell 中所有可用的数字类型(复杂的 iirc 除外)都是 Enum 类的成员。但这不是一个好主意,因为您通常有Int 参数,并且多余的函数调用会减慢您的程序速度。更好地显式转换,除非您的函数预计与许多不同的类型一起使用。实际上,不要太担心微优化。 ghc 有一个复杂且有点神秘的优化基础架构,可以让大多数程序运行得非常快。

修改

跟进一、二、三

是的,你的推理是正确的。

跟进4

如果您不给day_of_year 的浮点变体提供类型签名,则其类型默认为day_of_year :: (Integral a, Integral a2, Integral a1) => a -> a1 -> a2 -> a2。这实质上意味着:daymonthyear 可以是实现Integral 类型类的任意类型。该函数返回一个与day 相同类型的值。在这种情况下,aa1a2 只是不同的类型变量 - 是的,Haskell 也有类型级别的变量(以及种类级别 [这是type],但那是另一回事)- 可以满足任何类型。所以如果你有

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

变量a 被实例化为Int16a1 变为Int8a2 变为Integer。那么这种情况下的返回类型是什么?

Integer,看看类型签名!

跟进 5

事实上,你既是又不是。使类型尽可能通用当然有其优点,但它会使类型检查器感到困惑,因为当没有显式类型注释的术语中涉及的类型过于通用时,编译器可能会发现不止一种可能键入一个术语。这可能会导致编译器通过一些标准化但有些不直观的规则来选择类型,或者它只是用一个奇怪的错误来迎接你。

如果您真的需要通用类型,请争取类似

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

即:参数可以是任意的Integral 类型,但所有参数必须具有相同的类型。

永远记住 Haskell 从不强制类型转换。当涉及(自动)强制转换时,几乎不可能完全推断类型。您只能手动投射。现在有些人可能会告诉你Unsafe.Coerce 模块中的函数unsafeCoerce,它的类型为a -> b,但你实际上并不想知道。它可能不会像你认为的那样做。

跟进 6

div 没有任何问题。当涉及负数时,差异开始出现。现代处理器(如 Intel、AMD 和 ARM 制造的处理器)在硬件中实现 quotremdiv 也使用这些操作,但会进行一些操作以获得不同的行为。当您并不真正依赖负数的确切行为时,这会不必要地减慢计算速度。 (实际上有几台机器实现了div,但没有在硬件上实现quot。我现在唯一记得的是

【讨论】:

  • 谢谢 :) 您的回答让我意识到TypesTypeClasses 之间存在差异!我有很多后续问题可以发表评论;))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-21
相关资源
最近更新 更多