【问题标题】:Type Specification in a Where ClauseWhere 子句中的类型规范
【发布时间】:2016-10-23 17:48:11
【问题描述】:

我正在尝试做一些非常简单的事情作为家庭作业的一部分。我需要做的就是编写一个函数,它接收代表三角形底边和高度长度的 2 元组数字列表,并返回与这些三角形对应的区域列表。其中一个要求是我通过定义一个函数并在where 子句中声明它的类型来做到这一点。到目前为止我尝试过的所有东西都无法编译,这就是我所得到的:

calcTriangleAreas xs = [triArea x | x<-xs]
    where triArea:: (Num, Num) -> Num --this uses 4 preceding spaces 
triArea (base, height) = base*height/2

这失败并出现错误The type signature for ‘triArea’ lacks an accompanying binding,对我来说这听起来像是 triArea 没有在 where 子句中定义。好的,让我们缩进它以匹配where

calcTriangleAreas xs = [triArea x | x<-xs]
    where triArea:: (Num, Num) -> Num --this uses 4 preceding spaces 
    triArea (base, height) = base*height/2 --... and so does this

这个无法编译特别无信息的错误消息parse error on input triArea。只是为了好玩,让我们尝试多缩进一点,因为不知道还能做什么:

calcTriangleAreas xs = [triArea x | x<-xs]
    where triArea:: (Num, Num) -> Num --this uses 4 preceding spaces 
        triArea (base, height) = base*height/2 --this has 8

但是,没有骰子,失败并显示相同的parse error 消息。我尝试用等效的 4 空格制表符替换其中每一个中的间距,但这并没有 帮助。前两个使用制表符产生与使用空格相同的错误,但最后一个如下所示:

calcTriangleAreas xs = [triArea x | x<-xs]
    where triArea:: (Num, Num) -> Num --this uses a preceding tab character 
        triArea (base, height) = base*height/2 --this has 2

给出错误信息

Illegal type signature: ‘(Num, Num) -> Num triArea (base, height)’
  Perhaps you intended to use ScopedTypeVariables
In a pattern type-signature

我不知道那是什么意思,但它似乎突然忽略了换行符。我一直在阅读“Learn You a Haskell”,并且我应该能够使用前三章中提供的信息来做到这一点,但是我已经搜索了那些并且他们从未指定函数定义的类型在那些章节的where 子句中。作为记录,他们的例子似乎与间距无关,我复制了其中一个的风格:

calcTriangleAreas xs = [triArea x | x<-xs]
    where triArea:: (Num, Num) -> Num --4 preceding spaces
          triArea (base, height) = base*height/2 --10 preceding spaces

但这也编译失败,吐出完全无法理解的错误信息:

Expecting one more argument to ‘Num’
    The first argument of a tuple should have kind ‘*’,
      but ‘Num’ has kind ‘* -> GHC.Prim.Constraint’
    In the type signature for ‘triArea’: triArea :: (Num, Num) -> Num
    In an equation for ‘calcTriangleAreas’:
        calcTriangleAreas xs
          = [triArea x | x <- xs]
          where
              triArea :: (Num, Num) -> Num
              triArea (base, height) = base * height / 2

当我 google/hoogle 时我找不到任何东西,我查看了 this question,但它不仅显示了 haskell 高级让我阅读,但根据内容我不相信他们和我有同样的问题。我尝试指定calcTriangleAreas 的类型,并尝试将triArea 规范中的类型别名为Floating,坦率地说,我已经走到了尽头。我的文件的第一行是module ChapterThree where,但除此之外,我在每个示例中显示的代码就是整个文件。

我正在使用 32 位 Linux Mint 18,我正在使用 ghc ChapterThree.hs Chapter3UnitTests.hs -o Test 进行编译,其中 ChapterThree.hs 是我的文件,单元测试由我的老师提供,因此我可以轻松判断我的程序是否有效(它从来没有进入ChapterThreeUnitTests.hs的编译步骤,所以我认为内容并不重要),我的ghc版本是7.10.3。

编辑:请注意,如果我完全删除类型规范,一切都编译得很好,并且该函数通过了所有相关的单元测试。

请救我脱离我的疯狂。

【问题讨论】:

    标签: haskell ghc


    【解决方案1】:

    您的最后一个示例是正确的,但是您编写的类型没有意义。 Num类约束 而不是类型。你可能想写:

    calcTriangleAreas xs = [triArea x | x<-xs]
        where triArea:: Num a => (a, a) -> a
              triArea (base, height) = base*height/2 
    

    规则是:分配必须对齐。

    此外,(/) 需要 Fractional 类:

    calcTriangleAreas xs = [triArea x | x<-xs]
        where triArea:: Fractional a => (a, a) -> a
              triArea (base, height) = base*height/2 
    

    请注意,缩进级别where 的缩进级别有任何关系。例如,您可以这样编写代码:

    calcTriangleAreas xs = [triArea x | x<-xs] where
        triArea:: Fractional a => (a, a) -> a
        triArea (base, height) = base*height/2 
    

    缩进级别由where/let 中的第一个赋值或do 块的第一行定义。所有其他行必须与该行对齐。

    所以所有这些都是正确的:

    f x = y where
      a = b
      y = ...
    
    f x = y
      where a = b
            y = ...
    
    f x = y
      where
        a = b
        y = ...
    

    【讨论】:

    • @bfieck 请不要使用标签。每个缩进使用两个空格。有了你,每个人都会更开心:-)
    • @bfieck 制表符不是缩进敏感语言代码缩进的好方法。 很多 人因此而遇到一些错误,因为在使用制表符缩进时混合制表符和空格是非常容易和灾难性的。另外:编译器将制表符视为 8 个空格,但您的编辑器可能将其视为 2 或 4,这意味着您在编辑器中看到的可能不是编译器看到的,这增加了出错的机会。甚至有人提议在 Haskell 中完全禁止制表符缩进。
    • 不鼓励使用制表符因为 Haskell 不会在任何级别强制执行任何特定数量的缩进。由于不同用户可能会将选项卡显示为不同数量的空白,因此 明显 缩进可能与 实际 缩进不同。
    • @bfieck 当混合制表符和空格时,在大多数编辑器中,很容易查看似乎正确缩进的代码,而实际上并非如此。从历史上看,Haskell 98 委员会决定在检查缩进时制表符算作 8 个空格。更严格的要求会更好。今天,如果您使用标签,GHC 会警告您。有good proposals,但大多数社区认为从长远来看避免使用标签更简单。
    • 使用 only 制表符会更糟,因为现在我的缩进必须在每个级别缩进相同的数量,而不考虑启动缩进的构造。 let 表达式的第二行和后面的行通常缩进 4 个空格,而 where 表达式的那些缩进 6 个空格。许多人将类型签名与每行一种类型对齐,缩进由函数名的长度指定;类似的逻辑适用于使用记录语法定义的数据类型。没有固定的标签大小可以适应这种风格。
    【解决方案2】:

    吐出完全无法理解的错误信息:

    Expecting one more argument to ‘Num’
       The first argument of a tuple should have kind ‘*’,
         but ‘Num’ has kind ‘* -> GHC.Prim.Constraint’
    

    为了补充 Bakuriu 的答案,让我为你解码。

    错误表明--逐行:

    • Num 期待另一个论点——我们应该从一些 a 中写出 Num a
    • (,) 这样的元组类型需要一个类型作为参数。语句“应该有类型*”的意思是“应该是一个类型”。 Haskell 的分类系统将* 称为“类型的种类”。我们有例如Int :: *String :: *(Maybe Char, [Int]) :: *Maybe[] 等一元类型构造函数不是类型,而是从类型到类型的函数。我们写Maybe :: *-&gt;*[] :: *-&gt;*。他们的*-&gt;* 可以说明,由于Maybe :: *-&gt;*Char :: *,我们有Maybe Char :: *(“是一个类型”)类似于普通的值级函数。 pair 类型构造函数有 kind (,) :: *-&gt;*-&gt;*:它需要两种类型并提供一种类型。
    • Num 有一种*-&gt; Constraint。这意味着,对于每种TNum T 的类型将是Constraint,而不是*,正如(,) 所期望的那样。这会触发一种错误。 Constraint 类型用于类型类约束,例如 Eq IntOrd BoolNum Int。这些不是类型,而是对类型的要求。当我们使用(+) :: Num a =&gt; a-&gt;a-&gt;a 时,我们看到(+) 适用于任何类型a,只要该类型满足Num a,即是数字。由于Num T 不是类型,我们不能写Maybe (Num T)[Num T],我们只能写例如Maybe a 并要求 a 属于类型类 Num

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-11
      相关资源
      最近更新 更多