我想创建一个类型安全递归函数来展平元组。 但是,就类型安全而言,我不能低于第一个递归级别
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,第二个例子是编译错误。
祝你好运
答案 0 :(得分:3)
.Net Tuple
类的类型参数数量为arities from 1 to 8。我相信在F#中,如果你有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>>
类型的元组。
然而,你的第一种方法只处理arities 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]
当然,从1到8,您需要为这些arities中的每一个实现相应的static member inline ($)
实现。这是否可以解决您的问题?
P.S。请注意,我只是将此代码输入到Stack Overflow中的答案窗口;我还没有真正测试过它。
答案 1 :(得分: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
。因此,编译时场景总是会解析为原子情况,而永远不会解析为元组。