Reputation: 9934
I want to create a type safe recursive function for flattening out tuples. However I can not get below the first recursion level in terms of type safety
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
I tried another approach
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))
but I cant get that one to type check.
Does anybody have a clue?
One Additional Requirement
The reason I am doing all of this is partly because I want
let a1 = flatten (1, (2, 2, 3), (3,3))
to yield
val a1 : int list
That is because when I feed in a tuple of tuple of int then the only sensible result should be a int list
.
at the moment I get an obj list
int the first example a compile error in the second.
Best regards
Upvotes: 2
Views: 462
Reputation: 5751
I would like to wager the guess that this cannot be done in a type-safe way without a runtime type test.
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|]
Inline overload resolution does not work, because F#'s type system isn't expressive enough to discern between a type 'T
and a tuple 'T*'T
by way of member constraints; the tuple is necessarily treated as an atomic unit 'T
. Therefore, the compile-time scenario would always resolve to the atomic case and never to the tuples.
Upvotes: 2
Reputation: 36718
The .Net Tuple
class has arities from 1 to 8 in its number of type parameters. I believe that in F#, if you have a tuple of 8 or more elements, it's treated as a tuple of seven elements plus a nested tuple in the eight slot, e.g. (a,b,c,d,e,f,g,h,i,j)
is really (a,b,c,d,e,f,g,(h,i,j))
, a tuple of type System.Tuple<'T1,'T2,'T3,'T4,'T5,'T6,'T7,System.Tuple<'T8,'T9,'T10>>
.
However, your first approach only handles arities 2 and 3, yet you're testing it with an arity-4 tuple when you do flatten (1, (2, 2, 3, 4), (3,3))
. What if you rewrite your first Flat
function as follows?
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]
And, of course, you'd need corresponding static member inline ($)
implementations for each of these arities from 1 through 8. Does that solve your problem?
P.S. Note that I only just typed this code in to the answer window in Stack Overflow; I haven't actually tested it yet.
Upvotes: 4