删除签名标签后出现令人费解的类型不匹配

时间:2014-11-10 16:01:37

标签: ocaml

注意:这个问题实际上有两个重要方面:

  1. 如何从函数调用中省略签名标签(例如~f:~init:等);
  2. 如何调试复杂类型不匹配(特别是如何从编译器错误消息中的长复合类型签名中提取可操作信息)。
  3. 我希望答案能解决这两个问题。


    以下函数按预期编译并执行:

    open Core.Std
    
    let tally s =
      let upd m k =
        String.Map.change m k (function None -> Some 1 | Some n -> Some (n+1)) in
      let re = Str.regexp "[^a-zA-Z0-9]+" in
      let ws = List.map (Str.split re s) String.lowercase in
    
      List.fold_left ws ~init:String.Map.empty ~f:upd
    

    好的,现在:我想摆脱对List.fold_left(最后一行)的调用中的签名标签。我的理解是,只要参数按照函数文档中显示的顺序给出,我就可以这样做。

    List.fold_left的签名(如this page中所示)是:

    val fold_left : 'a t -> init:'b -> f:('b -> 'a -> 'b) -> 'b
    

    如果我正确读取此内容,我对List.fold_left的调用中的参数顺序相同,但如果我在最后一行中省略了签名标签,所以它变成

      List.fold_left ws String.Map.empty upd
    

    ...函数不再编译,编译器的错误消息显示:

    File "tally.ml", line 1:
    Error: The implementation tally.ml
           does not match the interface tally.cmi:
           Values do not match:
             val tally :
               bytes ->
               init:('a Core.Std.String.Map.t ->
                     (int Core.Std.String.Map.t ->
                      bytes -> int Core.Std.String.Map.t) ->
                     'b) ->
               f:(('a Core.Std.String.Map.t ->
                   (int Core.Std.String.Map.t ->
                    bytes -> int Core.Std.String.Map.t) ->
                   'b) ->
                  bytes ->
                  'a Core.Std.String.Map.t ->
                  (int Core.Std.String.Map.t ->
                   bytes -> int Core.Std.String.Map.t) ->
                  'b) ->
               'b
           is not included in
             val tally : bytes -> int Core.Std.String.Map.t
           File "tally.ml", line 3, characters 4-14: Actual declaration
    

    我的问题分为两部分:

    1. else (除了恰当地排序参数)我必须做的是能够在调用List.fold_left时省略签名标签吗?
    2. 是否可以从错误消息中找出解决方案(或至少进一步缩小问题)?
    3. (我希望通过这两个问题的答案,我将能够弄清楚为什么OCaml需要这里的标签来确定函数的类型与其界面匹配。)


      根据Jeffrey Scofield的评论,我尝试使用标准库的List模块重写函数:

      open Core.Std.String.Map
      open Core.Std.String
      
      let tally s =
        let upd m k =
          change m k (function None -> Some 1 | Some n -> Some (n+1)) in
        let re = Str.regexp "[^a-zA-Z0-9]+" in
        let ws = List.map lowercase (Str.split re s) in
      
        List.fold_left upd ws empty
      

      ...但它仍然无法编译:

      File "tally.ml", line 10, characters 17-20:
      Error: This expression has type int Map.t -> bytes -> int Map.t
             but an expression was expected of type ('a -> 'b) -> 'c -> 'a -> 'b
             Type
               int Map.t = (bytes, int, comparator_witness) Core_kernel.Core_map.t
             is not compatible with type 'a -> 'b
      

      这个List.fold_left的签名没有标签(正如杰弗里斯科菲尔德指出的那样),所以添加它们是没有意义的。

      我不能对此错误消息做任何正面或反面,即使它比前一个短得多。

      注意:为了使用标准库List.mapList.fold_left,我必须更改参数的顺序。

      顺便说一句:我绝对愤怒了解Core.Std.List.mapCore.Std.List.fold_left(以及谁知道还有什么)与标准库中相同命名的函数有不同的签名& #39; s List模块。如果有一个神圣的规则来重新实现库是尊重它的接口。违反此规则只会造成混乱,而且,随着语言的发展,OCaml已经比它应得的更加混乱。在我的书中,这种混乱的播撒是一种不可饶恕的罪,而且无论它有什么其他优势,都可以自行推理Core 。我现在急于从我的OCaml工作中清除它。

1 个答案:

答案 0 :(得分:0)

OCaml不需要标签来确定类型。为方便起见添加了标签,因此不应记住参数的顺序。所以标签只是一个关键字参数。您不应该删除它们,这是库实现者的决定,您应该使用它们。所以你应该使用标签,或选择其他库。实际上,没有标签的fold_left的可读性要低得多,所以删除它们无论如何都会是一个坏主意。

关于第二个问题,我认为这是非常易于理解的。它明确声明您的函数类型错误。

标签不仅仅是具有额外信息的扩充类型,它是类型声明的一部分,它创建的类型与未标记的类型不同。

表达

let f x = x

有类型

'a -> 'a

表达

let g ~x = x

具有完全不同的类型

x:'a -> 'a

如果没有带标签的参数,则无法应用g

而且,我不想这么说,但除此之外你会发现它...你尽管如此,我上面说过,OCaml确实允许你在某些情况下省略标签。但规则很微妙,我认为这是一个不好的做法。我不认为你应该把时间花在学习那些被认为是坏事的事情上。