15

I'm new to F# and trying to rewrite one of our applications in F# to try and learn it along the way and I am having a bit of trouble flattening a list. I have searched and found several answers, but I can't seem to get any of them to work.

My data type is saying it is val regEntries: RegistryKey list list

I would like it to only be one list.

Below is my code:

namespace DataModule

module RegistryModule =
    open Microsoft.Win32

let regEntries = 
    ["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
     "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"]
    |> List.map (fun x -> Microsoft.Win32.Registry.LocalMachine.OpenSubKey(x))
    |> List.map (fun k ->
        List.ofArray (k.GetSubKeyNames())
        |> List.map (fun x -> k.OpenSubKey(x))
        |> List.filter (fun x -> x.GetValue("ProductId") <> null))
Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
twreid
  • 1,453
  • 2
  • 22
  • 42

4 Answers4

14

Try the following

let regEntries = 
    ["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
     "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"]
    |> Seq.collect (fun p ->
       let k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(p)
       k.GetSubKeyNames()
       |> Seq.map (fun x -> k.OpenSubKey(x)) 
       |> Seq.filter (fun x -> x.GetValue("ProductId") <> null)))
    |> List.ofSeq

The Seq.concat method is useful for converting a T list list to a T list. Note that I switched a lot of your List. calls to Seq. calls. There didn't seem to be any need to create an actual list until the very end hence I kept it as a simple seq

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 10
    Note that you can replace the pipeline `Seq.map f |> Seq.concat` with the single partial function `Seq.collect f`, see [doc](http://msdn.microsoft.com/en-us/library/vstudio/ee340468%28v=vs.100%29.aspx) – kaefer Jan 10 '14 at 19:58
  • 3
    Also you can replace `Seq.map (fun x -> k.OpenSubKey(x))` with just `Seq.map k.OpenSubKey`. Similarly for the other call to OpenSubKey. – Joel Mueller Jan 10 '14 at 20:26
  • Hm, I edited in all the proposals but now see you were trying to simply answer the OP and teach Seq vs List and `concat` - feel free to revert the changes – Ruben Bartelink Oct 27 '22 at 08:07
6

Using the various map and filter functions is definitely an option - and it works great (and it is also great way to learn about functional abstractions).

However, you can also use sequence comprehensions, which is a nice syntactic sugar that makes writing these sort of tasks a bit easier (in my opinion). To do the same thing as what is happening in the answer by Jared, you can write:

let subKeys = 
  [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; //"
   @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"] // "

let regEntries = 
  [ for subKey in subKeys do     
      let k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(subKey)
      for name in k.GetSubKeyNames() do
        let x = k.OpenSubKey(name) 
        if x.GetValue("ProductId") <> null then
          yield x ]

The nesting simply becomes nested for loop and filtering is expressed using if - to produce a new value of the sequence you can use yield and the fact that the expression is enclosed in square brackets makes it a list comprehension.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
2

Flattening a list in F#, use the List.collect function with the id function as the predicate:

[ [1; 2]; [3; 4; 5]; [6] ] |> List.collect id

returns:

[1; 2; 3; 4; 5; 6]

Works fine for nested lists:

let myList = 
  [ 
    [ [1; 2]; [3; 4; 5] ];
    [ [6]; [] ]
  ]

myList 
  |> List.collect id
(* [[1; 2]; [3; 4; 5]; [6]; []] *)

myList
  |> List.collect id
  |> List.collect id
(* [1; 2; 3; 4; 5; 6] *)
Sam Eaton
  • 1,795
  • 1
  • 14
  • 19
1

Note that if you found this question looking for a basic way to flatten:

let flatten (source : 'T seq seq) :'T seq =
    System.Linq.Enumerable.SelectMany(source, id)

This is a basic call to the .net SelectMany with the F#'s id function.

aloisdg
  • 22,270
  • 6
  • 85
  • 105
  • Not sure what your point is, but `Seq.concat` is the most direct impl of your `flatten` (and `Seq.collect` is the equivalent of `SelectMany`) – Ruben Bartelink Oct 27 '22 at 08:10
  • 1
    @RubenBartelink `Seq.concat` works well! I did not know about it. Here is a [demo](https://tio.run/##lY/LCoJAFIb3PsWPENQize5xqIcoWomLwY4h2IzOjIvAdzcvg9SyxYFz@//vnMwsU6W5bcMQd5PLJ25cBaIsWT5gFVwmJITW4j20UOTGBl7BFoaryKpohfO3Lo4Ia8ImQdxgS9gR9oQD4Ug4Ebr1JvF@gKmSqbC9@5ix7MKRoLIRbibmlU1dWAd12riHOXDnT19sV7oDmgSJ1wtzyxrzrJbggl9YXlDqXNoM/uwBf2gupg@9cSbh@3@Jx1Pb9gM "F# (.NET Core) – Try It Online") from the [doc](https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ee353462(v=vs.100)). Thank you for pointing it out – aloisdg Oct 27 '22 at 08:24
  • :thumbsup: It's referenced and was used (until I mutilated it) in [the accepted answer](https://stackoverflow.com/a/21052218/11635) – Ruben Bartelink Oct 27 '22 at 09:11