2016-11-18 67 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