F# and ADO.NET - idiomatic F#

后端 未结 3 928
星月不相逢
星月不相逢 2021-02-04 01:55

I\'m just starting to learn F#. I wrote this F#/ADO.NET code last night. In what ways would you improve the syntax - make it feel like idiomatic F#?

    let cn =         


        
相关标签:
3条回答
  • 2021-02-04 02:17

    Well, there's not much you can change in the first bit but whenever you're processing collections of data like in the last few rows, you can use the built-in Seq, List, Array functions.

    for i in 0 .. (rowCount - 1) do
      let row:DataRow = rowCol.[i]
      printfn "%A" row.["LastName"]
    

    =

    rowCol |> Seq.cast<DataRow> 
           |> Seq.iter (fun row -> printfn "%A" row.["LastName"])
    
    0 讨论(0)
  • 2021-02-04 02:27

    I wrote a functional wrapper over ADO.NET for F#. With this library your example looks like this:

    let openConn() =
       let cn = new OleDbConnection(cnstr)
       cn.Open()
       cn :> IDbConnection
    
    let query sql = Sql.execReader (Sql.withNewConnection openConn) sql
    
    let people = query "select * from people" |> List.ofDataReader
    printfn "%d" people.Length
    people |> Seq.iter (fun r -> printfn "%s" (r?LastName).Value)
    
    0 讨论(0)
  • 2021-02-04 02:28

    The key part of your code deals with .NET API that is not functional, so there is no way to make this part of the code particularly more idiomatic or nicer. However, the key thing in functional programming is abstraction, so you can hide this (ugly) code into some idiomatic and reusable function.

    For representing collections of data in F#, you can either use standard F# list type (which is good for functional data processing) or seq<'a> (which is standard .NET IEnumerable<'a> under the cover), which works nicely when working with other .NET libraries.

    Depending on how you access database elsewhere in your code, the following could work:

    // Runs the specified query 'sql' and formats rows using function 'f'
    let query sql f = 
      // Return a sequence of values formatted using function 'f'
      seq { use cn = new OleDbConnection(cnstr) // will be disposed 
            let da = new OleDbDataAdapter(new OleDbCommand(sql, cn)) 
            let ds = new DataSet() 
            cn.Open() 
            let i = da.Fill(ds) 
            // Iterate over rows and format each row
            let rowCol = ds.Tables.[0].Rows 
            for i in 0 .. (rowCount - 1) do 
                yield f (rowCol.[i]) }
    

    Now you can use the query function to write your original code roughly like this:

    let names = query "SELECT * FROM People" (fun row -> row.["LastName"])
    printfn "count = %d" (Seq.count names)
    for name in names do printfn "%A" name
    
    // Using 'Seq.iter' makes the code maybe nicer 
    // (but that's a personal preference):
    names |> Seq.iter (printfn "%A")
    

    Another example you could write is:

    // Using records to store the data
    type Person { LastName : string; FirstName : string }
    let ppl = query "SELECT * FROM People" (fun row -> 
      { FirstName = row.["FirstName"]; LastName = row.["LastName"]; })
    
    let johns = ppl |> Seq.filter (fun p -> p.FirstName = "John")
    

    BTW: Regarding the suggestion by Mau I wouldn't use higher-order functions excessively if there is a more direct way to write the code using language constructs such as for. The example with iter above is simple enough and some people will find it more readable, but there is no general rule...

    0 讨论(0)
提交回复
热议问题