How can i convert between F# List and F# Tuple?

2019-01-11 23:00发布

Is there some way to convert between F# List and F# Tuple?

For example:

[1;2;3] -> (1,2,3)    
(1,2,3,4) -> [1;2;3;4]

I need two Functions to do that:

let listToTuple list = ...
let tupleToList tuple = ...

Thank you in advance.

标签: f#
5条回答
混吃等死
2楼-- · 2019-01-11 23:35

Besides listToTuple then pblasucci has the right answer. But you wont be happy with the result unless you know something about type types involved, or if you wan't to do a lot of boxing and unboxing.

let tupleToList t = 
    if Microsoft.FSharp.Reflection.FSharpType.IsTuple(t.GetType()) 
        then Some (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields t |> Array.toList)
        else None

let listToTuple l =
    let l' = List.toArray l
    let types = l' |> Array.map (fun o -> o.GetType())
    let tupleType = Microsoft.FSharp.Reflection.FSharpType.MakeTupleType types
    Microsoft.FSharp.Reflection.FSharpValue.MakeTuple (l' , tupleType)
查看更多
不美不萌又怎样
3楼-- · 2019-01-11 23:43

Making use of the PropertyInfo structure one can build a list recursively. A problem with this approach is that the type information is lost and the result is produced as a list of obj. Nevertheless, this does solve the tuple to list portion of the question.

let tupleToList tpl = 
    let rec loop tpl counter acc =
        let getItemPropertyInfo t n = t.GetType().GetProperty(sprintf "Item%d" n)
        let getItem t n = (getItemPropertyInfo t n).GetValue(t,null)
        match counter with
        | 8 -> 
            match tpl.GetType().GetProperty("Rest") with
            | null -> acc
            | _ as r ->
                let rest = r.GetValue(tpl,null)
                loop rest 2 ((getItem rest 1) :: acc)
        | _ as n -> 
            match getItemPropertyInfo tpl n with
            | null -> acc
            | _ as item -> loop tpl (counter+1) (item.GetValue(tpl,null) :: acc)
    loop tpl 1 [] |> List.rev
查看更多
淡お忘
4楼-- · 2019-01-11 23:44

As already pointed out, this is a tricky problem, because tuple isn't a single type - it is a family of types such as int * int * int or int * int and F# doesn't provide any way for taking the whole family of types as an argument. You can either write many similar functions (which is very uncomfortable) or use reflection (which is a bit slow and isn't type-safe).

Alternatively, you could limit the function to tuples with some structure - for example, instead of working with (1, 2, 3, 4), you could use nested tuples like (1, (2, (3, 4))). This is a bit less comfortable, but it keeps the type safety and it isn't as bad.

Then you can easily write combinators for constructing conversion functions on the fly:

// creates function for converting tuple (possibly with a nested 
// tuple in the second component to list
let tl f (a, b) = a::(f b)
// converts last element of the tuple to singleton list
let te a = [a]

Then you can combine functions tl and te to create a type-safe function that converts nested tuple containing 4 elements to a list like this:

let l = (1, (2, (3, 4))) |> (tl (tl (tl te)))

Similarly, you can create functions for converting list to tuples - note that this may throw an exception if the list doesn't match the expected format:

let le = function
  | [x] -> x
  | _ -> failwith "incompatible"
let lt f = function
  | [] -> failwith "incompatible"
  | x::xs -> (x, f xs) 

// convert list to a tuple of four elements
let t = [1; 2; 3; 4] |> lt (lt (lt le))

I guess this is probably as close to typesafe and reusable function for converting between tuples and lists as it can get. It isn't perfect (at all), but that's caused by the fact that you're trying to implement a very rarely used operation. In F#, the distinction between tuples and lists is clearer than for example in Python (which is dynamic, so it doesn't have to deal with static type safety).

查看更多
我只想做你的唯一
5楼-- · 2019-01-11 23:48

Actually you need 2*n functions where n is the greatest tuple size you want to support. A tuple containing three ints has a completely different type than a tuple containing four ints, so you need to write a tupleToList and a listToTuple function for each one separately.

Also note that you for listToTuple you need to know the size of the tuple you want to get at compile-time. I.e. you can't create a function that decides whether to return (int, int, int) or (int, int) depending on the length of the input list (since as I said, they're totally different types). You'd have to have a function listToNTuple that takes a list of at least N elements and returns a N-tuple.

It might be possible to write size-independent functions for this by using reflection, but since you couldn't know the type of any tuple returned by such a function at compile-time, it'd be quite painful to use.

查看更多
混吃等死
6楼-- · 2019-01-11 23:51

Well, it ain't pretty but:

let tuple_to_array input =
    let temp_str = input.ToString()
    temp_str.Substring(1, temp_str.Length - 2)
    |> Array.map (fun (x:string) -> x.TrimStart(' '))

I'm not sure how you'd go back the other way though. And I'm really not sure this would be sensible, but if you really have to.

查看更多
登录 后发表回答