2016-11-18 75 views
3

我建立一个函数,给定一个组合列表,返回两个名单:多类型列表

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. 

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

+0

是的。这让我想到了第二个问题:第一个例子中的'two elements'列表和第二个'three elements'列表有什么不同?它们都是由字符和整数组成的,毕竟。 – Worice

+0

对不起,我的第一个评论是无关紧要的。编译器不能推断函数的返回类型。您可以明确指出:'let rec split3(l:(char * int * char)list):(char list * int list * char list)=' – Petr

+1

更多关于值限制错误,因为它有时很难理解:https: //blogs.msdn.microsoft.com/mulambda/2010/05/01/finer-points-of-f-value-restriction/ – Petr

回答

6

简答

你正在寻找的功能已经存在于FSharp.CoreList.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保留原始列表的顺序。

+0

简单而有益。谢谢你的精彩答案!只是一个细节:是不必要的?而且,如评论中所述,列表不应该是同类型的? – Worice

+1

@Worice F#中轻量级和冗长语法之间的区别之一是对'in','begin'和'end'(详细)与使用缩进(轻量级)的要求。轻量级语法是默认的,几乎所有你见过的F#都使用它,所以'in'几乎总是不必要的。在这里看到更多:https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/verbose-syntax – TheInnerLight

+0

谢谢,这是我错过了! – Worice