12

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))
ildjarn
  • 60,915
  • 9
  • 122
  • 205
twreid
  • 1,423
  • 1
  • 22
  • 42

4 Answers4

12

Try the following

let regEntries = 
    ["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"]
    |> Seq.map (fun x -> Microsoft.Win32.Registry.LocalMachine.OpenSubKey(x))
    |> Seq.map(fun k -> 
       (k.GetSubKeyNames()) 
       |> Seq.map (fun x -> k.OpenSubKey(x)) 
       |> Seq.filter (fun x -> x.GetValue("ProductId") <> null)))
    |> Seq.concat
    |> 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

ildjarn
  • 60,915
  • 9
  • 122
  • 205
JaredPar
  • 703,665
  • 143
  • 1,211
  • 1,438
  • 9
    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
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
  • 234,359
  • 19
  • 362
  • 534
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
  • 19,231
  • 5
  • 81
  • 97
0

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]
Sam Eaton
  • 1,680
  • 1
  • 13
  • 19