【问题标题】:Why am I getting this error for a function that simply adds a value to every integer in a list?为什么我会为一个简单地为列表中的每个整数添加一个值的函数出现此错误?
【发布时间】:2021-11-15 14:01:00
【问题描述】:

这是我的功能:

fun inclist [] 0 = []
  | inclist (l, inc) = map (fn x => x + inc) l;

l 是一个整数列表,我正在尝试将inc 添加到l 中的每个整数。

但我遇到了这些错误

Function: inclist : int list * int -> int list
Argument: [1, 2, 3, 4, 5] : int list
Reason: Can't unify int list to int list * int (Incompatible types)

Function: inclist [1, 2, 3, 4, ...] : int list
Argument: 1 : int
Reason: Value being applied does not have a function type

我不明白为什么,因为我有一个几乎相同的函数,可以将值相乘,效果很好。

【问题讨论】:

  • 那应该在你开始应用这个函数之前就给你错误了。我的 poly 说“错误:这个子句有 1 个参数,但前面的子句有 2 个”。如果我创建第一个子句inclist ([], 0),我可以重现您的错误。复制粘贴的时候有没有搞错?
  • @Grav,已经一个星期了,您收到了多个很好的答案。接受您认为回答您的问题的任何答案是一种很好的形式。

标签: sml polyml


【解决方案1】:

为了澄清之前的答案,鉴于 OP 的代码暗示了 SML 知识的新手水平:

每个 SML 函数都采用 一个 参数。现在,这听起来很疯狂,因为我们都见过需要多个参数的函数。有两种方法可以调和这两件事。

柯里化

函数是 SML 中的第一类值。我们可以很容易地编写一个函数而不给它一个名字。

fn x => x + 1

我们可以把它绑定到一个名字上。

val add1 = fn x => x + 1

这些函数的类型为int -> int。此类型签名表明该函数接受一个 int 并返回一个 int。

有一个方便的语法。

fun add1 x = x + 1

如果我们想让一个函数接受多个参数......我们不能。但是我们接受一个 int 的函数可以返回一个接受一个 int 并返回一个 int 的函数。这种函数的类型签名是int -> int -> int

val add = fn x => fn y => x + y

再一次,有一个更方便的语法来写这个。

fun add x y = x + y

调用这个很简单:

add 42 27

但我们可以部分应用该功能。例如,将 1 提供给 add 并返回一个将 1 添加到发送给它的任何 int 的函数。

val add1 = add 1

元组

SML 的表亲 OCaml 几乎完全使用柯里化。在 SML 中,使用元组为函数提供多个参数通常更为惯用。

元组是单个值,但它包含多个可能具有异构类型的值。考虑两个整数的简单元组:(3, 4)。从语法上讲,元组用括号括起来,值用逗号分隔。

一个像我们之前的 add 函数一样添加两个整数但使用元组的函数如下所示:

fun add (x, y) = x + y

在很多情况下,函数名和元组之间的空格被省略了。对于那些熟悉类 C 语法的人来说,这可能会误导实际发生的事情。

fun add(x, y) = x + y

您的代码

那么,看看你写的代码:

fun inclist [] 0 = []
  | inclist (l, inc) = map (fn x => x + inc) l;

第一行定义了'a list -> int -> 'a list 类型的柯里化函数,第二行定义了int list * int -> int list 类型的函数。

由于这两个签名不匹配,您的代码无法编译。

还有一件事

在您的代码中,您不必要地专门化了模式匹配。

fun inclist [] 0 = []

您不需要将第二个参数匹配为0。将空列表增加 any 量将始终是一个空列表。

【讨论】:

    【解决方案2】:

    这不是您的实际代码 - 该代码不会产生这些消息。
    你的实际代码看起来更像这样:

    fun inclist ([], 0) = []
      | inclist (l, inc) = map (fn x => x + inc) l;
    
    inclist [1,2,3,4,5] 1;
    

    具有相同形状的定义子句。
    您已经定义了一个函数,该函数接受 int listintpair 并返回 int list
    正如错误消息所说:int list * int -> int list

    然后您尝试将此函数应用于不是这样一对的东西。
    inclist [1,2,3,4,5] 1(inclist [1,2,3,4,5]) 1 相同 - 它首先将inclist 应用于[1,2,3,4,5],然后应用结果 -这必须是一个函数 - 到1

    您的第一条消息指的是inclist [1,2,3,4,5],并说[1,2,3,4,5] 不是int list * intinclist 需要它。

    inclist [1,2,3,4,5] 不是函数时,第二条消息是指您将inclist [1,2,3,4,5] 应用于1
    (根据上一条消息,当它有一个类型错误时,polyml 声称inclist [1,2,3,4,5] 是一个int list,这有点奇怪。一种可能的解释是,polyml 不费心检查参数的类型,而是去“ inclist 应用于任何参数是 int list"。)

    解决方案是像函数期望的那样传递一对:

    inclist ([1,2,3,4,5], 1)
    

    或以“curried”方式定义函数:

    fun inclist [] 0 = []
      | inclist l inc = map (fn x => x + inc) l;
    
    inclist [1,2,3,4,5] 1;
    

    附带说明,第一个特殊情况是不必要的。
    如果你担心效率低下,特例 0[] 更有意义(避免在巨大列表中添加零可能是有益的;只有在空列表中避免它是没有意义的)。

    fun inclist (l, 0) = l
      | inclist (l, inc) = map (fn x => x + inc) l;
    

    但这也可以完成工作:

    fun inclist (l, inc) = map (fn x => x + inc) l;
    

    【讨论】:

      【解决方案3】:

      您的第一个模式以柯里化方式定义,而您的第二个模式使用元组方式。

      这应该可以工作:

      fun inclist [] _ = []
        | inclist l inc = map (fn x => x + inc) l;
      

      注意我稍微改变了模式,因为当列表为空时,我们真的不在乎增量是多少。

      事实是,这可能是 map 已经在做的事情,所以很可能您可以取消第一个模式并保留您的映射函数模式。

      但无论如何,现在你可以这样做了:

      inclist [] 100; 
      inclist [1,2,3] 5;
      

      或者,您也可以使用括号定义这两种函数模式。

      【讨论】:

        【解决方案4】:

        即使这样也可以......

        fun inclist inc = map (fn x => x + inc);
        

        现在可能不会,但在探索Higher-Order Functions的地盘的某个时间点,这件事会很容易理解。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-09-28
          • 1970-01-01
          • 1970-01-01
          • 2019-02-14
          • 2011-05-18
          • 1970-01-01
          • 2019-08-17
          • 2022-01-05
          相关资源
          最近更新 更多