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

f#
12,491

Solution 1

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)

Solution 2

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).

Solution 3

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.

Solution 4

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
Share:
12,491

Related videos on Youtube

kev
Author by

kev

Simplicity is the ultimate sophistication. — Leonardo Da Vinci less is more The basic problem is actually very complicated. It's amazing that computers only use 0s and 1s. handy tools # download bigfile $ aria2c -c -k1M -{x,j,s}16 --checksum=md5=dccff28314d9ae4ed262cfc6f35e5153 http://mirrors.zju.edu.cn/ubuntu-releases/14.04/ubuntu-14.04-desktop-amd64.iso # backup txt files $ rsync -avzm --include '*/' --include '*.txt' --exclude '*' source/ remote:target/ # transfer file at 100kB/s $ rsync -hP --bwlimit 100 remote:file . # convert json to csv $ jq -r '[.field1,.field2,.field3]|@csv' input.json > output.csv # http proxy $ ncat -v -l --proxy-type http 3128 # convert socks5 to http $ delegate -P8080 SERVER=http SOCKS=1.2.3.4:1080 [email protected] # convert socks5 to http with auth $ delegated -f -P8080 SERVER=http FORWARD=socks://user:[email protected]:1080 [email protected] # zero-padding file names $ rename 's/^\d+/sprintf("%02d", $&)/e' [0-9]* # concat video parts $ printf "file '%s'\n" part*.mp4 | ffmpeg -f concat -i - -c copy all.mp4 # scan raspberry pi $ sudo nmap -n -sP 192.168.1.0/24 -oX - | xmlstarlet sel -t -m '//host[address[contains(@vendor, "Raspberry Pi")]]/address[@addrtype="ipv4"]/@addr' -v . -n | ssh-keyscan -t rsa -f - | sort -u - ~/.ssh/known_hosts -o ~/.ssh/known_hosts # upgrade all outdated python packages $ pip list --outdated | awk '{print $1}' | xargs -tn1 pip install # generate birthday wordlist for aircrack-ng $ dateseq -f '%Y%m%d' 1970-01-01 2019-01-01 > birthday.txt # install python on raspberry pi $ ansible pi --limit pi2 -e ansible_user=alarm --ask-pass --su --ask-su-pass -m raw -a 'pacman -Sy --noconfirm python2' # resize sdcard for raspberry pi $ echo -e 'p\nd\n2\nn\np\n2\n\n\np\nw' | fdisk /dev/mmcblk0 $ reboot $ resize2fs /dev/mmcblk0p2 # authorized key initialization $ sshpass -p xxxxxx ssh-copy-id -o StrictHostKeyChecking=no user@server # open google chrome in full-screen mode $ open -a 'Google Chrome' --args --kiosk https://github.com/ # customized hostname $ curl --resolve 'httpbin:80:23.22.14.18' http://httpbin/headers # add ssh pubkey to multiple servers $ ansible rpi -m authorized_key -a 'user=pi key="ssh-rsa ..."' # brute force attack openwrt $ hydra -l root -P password.list 192.168.1.1 http-form-post '/cgi-bin/luci:luci_username=^USER^&luci_password=^PASS^:S=302 Found' # dump obs-studio events $ websocat -t -u autoreconnect:ws://127.0.0.1:4444 reuse:appendfile:obs.jl

Updated on March 22, 2020

Comments

  • kev
    kev about 4 years

    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.

    • Nels Beckman
      Nels Beckman almost 14 years
      This is going to be a hard problem precisely because tuples are meant to be sized statically while lists are meant to be sized dynamically. I think that sepp2k's answer is probably the best if you really need such a solution... define one function for each size up to the largest list size you'll need and then choose the function to call based on the list length.
  • Daniel
    Daniel almost 14 years
    Hmmm. That seems like it would only work with string serializable tuple containers. Not sure I like this solution.
  • Massif
    Massif almost 14 years
    aye, it's extremely hacky, but I only had five minutes to prove the concept. Huusom's solution is infinitely better.
  • tina Miller
    tina Miller about 11 years
    Of course the other thing is that tuples aren't just (int * int * int) but also (int * float * string), which doesn't combine well with a list.
  • Trident D'Gao
    Trident D'Gao over 10 years
    I am struggling to find any documentation for the followin syntax. Would you help please? let le = function | [x] -> x | _ -> failwith "incompatible"
  • Istvan
    Istvan almost 3 years
    @TridentD'Gao function syntax is a syntactic sugar for match.