Remo H. Jansen
Remo H. Jansen

Reputation: 24941

F# pipelines and LINQ

I'm starting to learn F#. I'm finding quite dificult to change my mind from OO programing. I would like to know how would a F# developer write the following:

"Traditional" C#

public List<List<string>> Parse(string csvData){
    var matrix = new List<List<string>>();
    foreach(var line in csvData.Split(Environment.NewLine.ToArray(), StringSplitOptions.None)){
        var currentLine = new List<string>();
        foreach(var cell in line.Split(','){
            currentLine.Add(cell);
        }
        matrix.Add(currentLine);
    }
    return matrix;
}

"Functional" C#

public List<List<string>> Parse(string csvData){
    return csvData.Split(Environment.NewLine.ToArray(), StringSplitOptions.None).Select(x => x.Split(',').ToList()).ToList();
}

The Question is: Would the code below considered right?

F#

let Parse(csvData:string) = 
    csvData.Split(Environment.NewLine.ToArray(), StringSplitOptions.None).ToList()
    |> Seq.map(fun x -> x.Split(',').ToList())

Upvotes: 1

Views: 650

Answers (3)

Tomas Petricek
Tomas Petricek

Reputation: 243041

Your translation looks good to me. The use of extension method in C# (such as foo.Select(...)) is roughly equivalent to the use of pipeline and F# function from List, Seq or Array modules, depending on which collection type you're using (e.g. foo |> Seq.map (...)).

It is perfectly fine to use LINQ extension methods from F# and mix them with F# constructs, but I would only do that when there is no corresponding F# function, so I would probably avoid ToArray() and ToList() in the sample and write:

open System

let Parse(csvData:string) = 
    // You can pass the result of `Split` (an array) directly to `Seq.map`
    csvData.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.None)
    // If you do not want to get sequence of arrays (but a sequence of F# lists)
    // you can convert the result using `Seq.toList`, but I think working with
    // arrays will be actually easier when processing CSV data
    |> Seq.map(fun x -> x.Split(',') |> Seq.toList)

Upvotes: 5

Daniel
Daniel

Reputation: 47904

There are subtle differences between the various solutions on this page. For example, your C# solutions and pad's solution are eager, but your F# version and Tomas' are lazy. Yours and pad's split on Environment.NewLine, but Tomas' splits on any char in NewLine (I'm guessing you want the latter since you called the overload that accepts StringSplitOptions). Maybe these matter; maybe not.

Anyway, here's a different take on pad's solution.

let split (delims: char[]) (str: string) =
  str.Split(delims, StringSplitOptions.RemoveEmptyEntries)

let parse csvData =
  let nl = Environment.NewLine.ToCharArray()
  [ for x in split nl csvData -> split [|','|] x ]

Upvotes: 1

pad
pad

Reputation: 41290

There are a few things not very F#-ish in your solution:

  • mutable List is named ResizeArray in F# and they don't have good support as built-in collections including Array, List, Seq.
  • You don't often need to use LINQ in F#, Array, List and Seq modules are more than adequate.
  • dot (.) syntax doesn't play nicely with pipe operators.

Changing the return type to string [] [], you could make more succinct code with a helper function:

let split (delim: string) (str: string) =
    str.Split([|delim|], StringSplitOptions.RemoveEmptyEntries)

let parse (csvData: string) = 
    csvData
    |> split Environment.NewLine
    |> Array.map (split ",")

Upvotes: 3

Related Questions