2017-05-12 22 views
2

我想創建一個類型安全的遞歸函數來扁平化元組。 但是我不能讓下面的第一個遞歸級別的類型安全我如何編寫類型安全的遞歸內聯函數來扁平化元組

type Flatten = Flatten 
with 
    static member inline ($) (Flatten, (a: 'a, b: 'b)) : 'x list = 
     List.concat [ Flatten.Flat a; Flatten.Flat b] 
    static member inline($) (Flatten, (a: 'a, b: 'b, c: 'c)) : 'x list = 
     List.concat [Flatten.Flat a; Flatten.Flat b; Flatten.Flat c] 
    static member inline Flat(x: obj) : 'x list = 
     match x with 
     | :? Tuple<'a, 'b> as t -> Flatten $ (t.Item1, t.Item2) 
     | :? Tuple<'a, 'b, 'c> as t ->Flatten $ (t.Item1, t.Item2, t.Item3) 
     | _ -> [x] 
let inline flatten x = Flatten $ x 
let a1 = flatten (1, (2, 2, 3), (3,3)) 
//this compiles 
let a2 = flatten (1, (2, 2, 3, 4), (3,3)) 
//       ^but this too 

方面我嘗試另一種方法

type Flatten = Flatten 
with 
    static member inline ($) (Flatten, (a: 'a, b: 'b)) = List.concat [ Flat $ a; Flat $ b] 
    static member inline ($) (Flatten, (a: 'a, b: 'b, c: 'c)) = List.concat [Flat $ a; Flat $ b; Flat $ c] 

and Flat = Flat 
with 
    static member inline ($) (Flat, a: 'a) = [a] 
    static member inline ($) (Flat, x: ('a *'b)) = 
     let (a, b) = x 
     List.concat [ Flatten $ a; Flatten $ b] 
    static member inline($) (Flat, x : ('a * 'b * 'c)) = 
     let (a, b, c) = x 
     List.concat [Flatten $ a; Flatten $ b; Flatten $ c] 

let inline flatten x = Flatten $ x 
let a = flatten (1, 1) 
let a1 = flatten (1, 1, 3) 
let a2 = flatten (1, 1, (3, 3)) 

,但我不能讓一個鍵入檢查。

有沒有人有線索?

一個額外的要求

我做這一切的原因,部分是因爲我想

let a1 = flatten (1, (2, 2, 3), (3,3)) 

產生

val a1 : int list 

這是因爲,當我在喂int的元組的元組,那麼唯一明智的結果應該是int list。 此刻我得到一個obj list int第一個例子在第二個編譯錯誤。

此致

+1

當然,你不能所有的元組大小。在某些時候,你必須指定儘可能多的重載元素。 – Gustavo

+1

我只是放棄靜態鍵入保證並使用反射。 – scrwtp

+1

@Gustavo - 由於F#只有元組大小1-8,並且「具有嵌套元組的更大元組大小」,所以我認爲實際上可以爲所有元組大小寫這個。不過,我從來沒有覺得有必要這樣做,因爲通常有更好的(恕我直言)解決方案來解決無論什麼問題都會導致你想要壓扁未知元組的元組。 – rmunn

回答

3

在.NET Tuple類有arities from 1 to 8在其號碼的類型的參數。我相信在F#中,如果有8個或更多元素的元組,它將被視爲7個元素的元組加上8個槽中的嵌套元組。 (a,b,c,d,e,f,g,h,i,j)實際上是(a,b,c,d,e,f,g,(h,i,j)),這是一個System.Tuple<'T1,'T2,'T3,'T4,'T5,'T6,'T7,System.Tuple<'T8,'T9,'T10>>類型的元組。

但是,您的第一種方法只能處理2號和3號區域,但當您執行flatten (1, (2, 2, 3, 4), (3,3))時,您正在用arity-4元組進行測試。如果你重寫你的第一個Flat函數如下?

static member inline Flat(x: obj) : 'x list = 
    match x with 
    | :? Tuple<'a> as t -> Flatten $ (t.Item1) 
    | :? Tuple<'a, 'b> as t -> Flatten $ (t.Item1, t.Item2) 
    | :? Tuple<'a, 'b, 'c> as t ->Flatten $ (t.Item1, t.Item2, t.Item3) 
    | :? Tuple<'a, 'b, 'c, 'd> as t -> Flatten $ (t.Item1, t.Item2, t.Item3, t.Item4) 
    | :? Tuple<'a, 'b, 'c, 'd, 'e, 'f> as t -> Flatten $ (t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6) 
    | :? Tuple<'a, 'b, 'c, 'd, 'e, 'f, 'g> as t -> Flatten $ (t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7) 
    | :? Tuple<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> as t -> Flatten $ (t.Item1, t.Item2, t.Item3, t.Item4, t.Item5, t.Item6, t.Item7, t.Item8) 
    | _ -> [x] 

,當然,你需要相應static member inline ($)實現對每個arities從1到8這是否解決您的問題嗎?

P.S.請注意,我只是將此代碼鍵入Stack Overflow中的答案窗口;我還沒有真正測試過它。

+0

thx的答案 - 可惜它不是我想要的。請看看我上面的評論。反正無論如何 – robkuz

2

我想打賭,如果沒有運行時類型測試,不能以類型安全的方式進行這種猜測

module Tuple = 
    open Microsoft.FSharp.Reflection 
    let rec collect<'T> (x : obj) = [| 
     if FSharpType.IsTuple <| x.GetType() then 
      for y in FSharpValue.GetTupleFields x do 
       yield! collect y 
     elif x :? 'T then yield x :?> 'T |] 

Tuple.collect<int> (((100,101,102),"X"),1,2,3,(4,5)) 
// val it : int [] = [|100; 101; 102; 1; 2; 3; 4; 5|] 

內嵌重載方案不起作用,因爲F#的類型系統是不夠的表達類型'T並通過構件的約束方式的元組'T*'T之間進行辨別;該元組必須被視爲原子單元'T。因此,編譯時方案將始終解析爲原子大小寫,而不是元組。