【问题标题】:Multi-type lists多类型列表
【发布时间】:2016-11-18 22:48:13
【问题描述】:

我构建了一个函数,给定一个组合列表,返回两个列表:

let rec split2 l =
    match l with 
    [] -> ([], [])
    | (x, y)::ls -> let (xs, ys) =
                                split ls in (x::xs, y::ys);;  

val split2 : l:('a * 'b) list -> 'a list * 'b list

lsts = [('a', 1); ('b', 2); ('c', 3); ('d', 4)]

split2 lsts;;
val it : int list * char list = ([1; 2; 3; 4], ['a'; 'b'; 'c'; 'd'])

现在,我将这个概念应用于更复杂的列表:

let l1 = [('a', 1, 'a'); ('b', 2, 'b'); ('c', 3, 'c'); ('d', 4, 'd')]

我使用的函数出现了类型问题,所以我构建了第二个。在这种情况下,我已经仔细定义了类型,但是当它应用于l1 时仍然返回错误,即使它编译。

let rec split3 (l:(char * int * char) list) =                  
    match l with 
    [] -> ([], [], [])
    | (x, y, z)::ls -> 
                    let (xs, ys, zs) = 
                                    split3 ls in (xs, ys, zs);; 

val split3 : l:(char * int * char) list -> 'a list * 'b list * 'c list

split3 l1;;

    error FS0030: Value restriction. The value 'it' has been inferred to 
    have generic type val it : '_a list * '_b list * '_c list    
    Either define 'it' as a simple data term, make it a function with explicit 
arguments or, if you do not intend for it to be generic, add a type annotation.

为什么,即使声明了类型,也需要进一步的类型注释?

【问题讨论】:

  • 确实如此。这让我想到了第二个问题:第一个示例中的“两个元素”列表和第二个示例中的“三个元素”列表有什么区别?毕竟它们都是由字符和整数组成的。
  • 对不起,我的第一条评论无关紧要。编译器无法推断函数的返回类型。您可以明确表示:let rec split3 (l:(char * int * char) list) : (char list * int list * char list) =
  • 关于价值限制错误的更多信息,因为有时很难理解:blogs.msdn.microsoft.com/mulambda/2010/05/01/…
  • 这些函数已经存在于核心 F# 库中;他们被称为List.unzipList.unzip3

标签: list types f#


【解决方案1】:

简答

您要查找的函数已在FSharp.Core 中作为List.unzip3. 存在

List.unzip3 : ('T1 * 'T2 * 'T3) list -> 'T1 list * 'T2 list * 'T3 list

长答案

您描述的两个函数是不同的。注意split3函数的类型签名是:

val split3 : l:(char * int * char) list -> 'a list * 'b list * 'c list

这没有意义。类型签名应该是:

val split3 : l:(char * int * char) list -> char list * int list * char list

那么,为什么不是呢?

请注意,在split2 函数中,您将结果定义为(x::xs, y::ys),在split3 中,您将结果定义为(xs, ys, zs)。这意味着您的 split3 函数的结果始终为 ([], [], []) 但未定义空列表的类型 - 因此出现值限制错误。

这很容易解决:

let rec split3 (l:(char * int * char) list) =                  
    match l with 
    | [] -> ([], [], [])
    | (x, y, z)::ls -> 
        let (xs, ys, zs) = split3 ls
        (x::xs, y::ys, z::zs)

纠正此问题后,您可以删除类型注释,因为现在可以正确推断函数的类型:

let rec split3 l =                  
    match l with 
    | [] -> ([], [], [])
    | (x, y, z)::ls -> 
        let (xs, ys, zs) = split3 ls
        (x::xs, y::ys, z::zs)

此外,这种类型的函数只是一个fold,所以如果你要手动编写它,最好按照那个高阶函数来编写它,而不是通过显式递归。

let split3 l =
    let folder (x, y, z) (xs, ys, zs) =
        (x::xs, y::ys, z::zs)
    List.foldBack folder l ([], [], [])

请注意,我使用 foldBack 而不是 fold 来保留原始列表顺序。

【讨论】:

  • 直截了当且具有启发性。谢谢你的精彩回答!只是一个细节:in 是不必要的吗?而且,正如 cmets 中所述,列表不应具有同质类型?
  • @Worice F# 中轻量级语法和详细语法之间的区别之一是需要inbeginend(详细)与使用缩进(轻量级)。轻量级语法是默认的,几乎所有你见过的 F# 都使用它,所以in 几乎总是不必要的。在此处查看更多信息:docs.microsoft.com/en-us/dotnet/articles/fsharp/…
  • 谢谢,我错过了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-19
  • 2012-10-03
  • 1970-01-01
  • 2015-11-06
相关资源
最近更新 更多