f# convert seq obj to seq records - f#

I am trying to read data from database and convert them to seq of records.
When executing
open FSharp.Data
open FSharp.Data.SqlClient
type foo = {name:int; author:int}
[<Literal>]
let query = "Select * from Authors"
[<Literal>]//DESKTOP-5RIV0K1
let connectionString = "Data Source=DESKTOP-5RIV0K1;Initial Catalog=TestBase;Integrated Security=True"
let cmd = new SqlCommandProvider<query,connectionString>(connectionString)
let result = cmd.Execute() |> printfn "%A"
result
I get
seq
[{ AuthorID = 1; Name = Some "2" }; { AuthorID = 2; Name = Some "3" };
{ AuthorID = 3; Name = Some "4" }]
But when I am trying to convert seq obj to seq foo with code below
let result =
for item in cmd.Execute() do
match item.Name, item.AuthorID with
| Some itemName, Some itemAuthor ->
{name = itemName; author = Some itemAuthor }
| _ -> ()
I get error
Some itemAuthor
Error FS0001 This expression was expected to have type
'int' but here has type
''a option'
What I am doing wrong?

You are trying to match records that look like this...
{ AuthorID = 1; Name = Some "2" }
...using a pattern that looks like this...
| Some itemName, Some itemAuthor ->
Notice that your AuthorID is an integer. It is not an Option<int>. Consequently, the pattern with two Some values is not compatible. You should have a pattern like this instead...
| Some itemName, itemAuthor ->

Related

In F#, how to update optional nested records?

(Newbie question).
I have been struggling with updating nested records with options in F#. How is this done?
Please assume:
module Visit =
type Model =
{
Id: int
Name: string
}
let initWithTime (t:DateTime) =
{
Id = 0
Name = sprintf "Time is %A" t
}
module Cell =
type Model =
{
Id: int
Visit: Visit.Model option
}
let setVisitFromInteger (i:int, m:Model) =
let appointmentTime =
DateTime.Today + TimeSpan.FromMinutes(float i)
{ m with Visit = { m.Visit
match m.Visit with
| Some x -> x with Name = sprintf "New appointment time %A" appointmentTime
| None -> Visit.initWithTime appointmentTime
}
}
Clearly, setVisitFromInteger is all wrong. How is a nested optional record correctly updated?
TIA
I think you just have a little confusion with the syntax. This is a correct version:
open System
module Visit =
type Model =
{
Id: int
Name: string
}
let initWithTime (t:DateTime) =
{
Id = 0
Name = sprintf "Time is %A" t
}
module Cell =
type Model =
{
Id: int
Visit: Visit.Model option
}
open Cell
let setVisitFromInteger (i:int, m:Model) =
let appointmentTime =
DateTime.Today + TimeSpan.FromMinutes(float i)
{ m with
Visit =
match m.Visit with
| Some x -> { x with Name = sprintf "New appointment time %A" appointmentTime }
| None -> Visit.initWithTime appointmentTime
|> Some
}
Note that the Visit in the record update expression is an option, not a record, so it doesn't need record syntax. However, the record syntax is required inside the pattern match since you're trying to do a nested record update expression.

How to write a csv in F#?

How would I write a record in F# into a csv? It would be optimal to have one row for each instance of a certain variable. My record and final output is a map like the one below.
type Family =
{ Month : int
Year : int
Income : float
Family : int
Dogs : int
Cats : int
}
let monthly =
timeMap
|> Seq.ofList
|> Seq.map(fun ((month,year), rows) ->
{ Month = month
Year = year
Income = rows.Inc
Family = familyMap.[(month,year)].Children
Dogs = familyMap.[(month,year)].Dogs
Cats = familyMap.[(month,year)].Cats
})
|> List.ofSeq
let map =
monthly
|> List.map (fun x -> (x.Year,x.Month),x)
|> Map.ofList
EDITED
This is what I have tried, but I am getting the error that (A,B,C,D,E,F) are not defined, and that it is recommended that I use the syntax new (type) args. This last error is showing up under >> MyCsvType
type MyCsvType = CsvProvider<Schema = "A (int), B (int), C (float), D (int), E (int), F (int)", HasHeaders = false>
let myCsvBuildRow (x:Family) = MyCsvType.Row(x.A,x.B,x.C,x.D,x.E,x.F)
let myCsvBuildTable = (Seq.map myCsvBuildRow) >> Seq.toList >> MyCsvType
let myCsv = monthly|> myCsvBuildTable
myCsv.SaveToString()
Your code is almost there, except that the myCsvBuildRow function needs to access members of the Family type using their correct names. In your version, you are accessing names such as A, B, etc., but those are the names of columns in your CSV file, not the names of members of the F# record. The following does the trick for me:
type MyCsvType = CsvProvider<Schema = "A (int), B (int), C (float), D (int), E (int), F (int)", HasHeaders = false>
let myCsvBuildRow (x:Family) =
MyCsvType.Row(x.Month,x.Year,x.Income,x.Family,x.Dogs,x.Cats)
let myCsvBuildTable data =
new MyCsvType(Seq.map myCsvBuildRow data)
let myCsv = family |> myCsvBuildTable
myCsv.SaveToString()

Carrying out same function on different F# record types with identical labels

Suppose I have following record types and their lists:
type Employee = {
id:int
name:string
}
type Project = {
id:int
name:string
}
let el = [{Employee.id = 1; name = "E1"};{Employee.id = 2; name = "E2"};{Employee.id = 3; name = "E3"};]
let pl = [{Project.id = 5; name = "P1"};{Project.id = 6; name = "P2"};{Project.id = 7; name = "P3"};]
I want to apply the same function(as defined below) to both type lists but the type inferred is Project.
let CreateFormattedStringList l =
l |> List.map(fun x -> (x.id |> string) + "#" + x.name)
//function signature:
//val CreateFormattedStringList : l:Project list -> string list
let res_1 = el |> CreateFormattedStringList //error
let res_2 = pl |> CreateFormattedStringList //ok
I found this helpful link which shows a simple value returned. So, the following works for both types of lists in my case:
let inline CreateFormattedStringList (l: ^T list) =
(^T: (member id:int) (l.Head))
Now I am unable to wrap my head around how to apply the more elaborate function in same way. Something like:
let inline CreateFormattedStringList (l: ^T list) =
l |> List.map(fun (^T: (member id:int) (x)) -> (x.id |> string) + "#" + x.name)
//error
I am trying to find examples but aren't able to. How can I use inline to be able to apply the same function to both types? Also, how to add constraint for 'name' and 'id' both?
Firstly, I think it's simpler to write a function that works on a single item instead of a list and then use it with other higher order functions like List.map if necessary.
The syntax for this is confusing, but what you had working so far was actually a function that contains an expression that uses the id member, while also asserting that the input type has an id member. So you need to add another expression for name. It's easier to tell what's going on if you bind those to names:
let inline formatIdName (x: ^T) =
let id = (^T: (member id:int) x)
let name = (^T: (member name:string) x)
sprintf "%i - %s" id name
formatIdName {Employee.id = 1; name = "E1"} // "1 - E1"
formatIdName {Project.id = 5; name = "P1"} // "5 - P1"

this expression was expected to have type IDataReader but here has type SqlDataReader

The following code is not casting my return value of SqlDataReader from getReader correctly to IDataReaderin the call to Seq.unfold. What am I doing wrong?
open System.Data
open System.Data.SqlClient
open System.Configuration
type Foo = { id:int; name:string }
let populateFoo (r:IDataReader) =
let o = r.GetOrdinal
{ id = o "id" |> r.GetInt32; name = o "name" |> r.GetString; }
let iter populateObject (r:IDataReader) =
match r.Read() with
| true -> Some(populateObject r, r)
| _ -> None
let iterFoo = iter populateFoo
let getReader : IDataReader =
let cnstr = ConfigurationManager.ConnectionStrings.["db"].ConnectionString
let cn = new SqlConnection(cnstr)
let cmd = new SqlCommand("select * from Foo", cn)
cmd.ExecuteReader()
let foos = Seq.unfold iterFoo getReader
F# does not automatic upcasting like C#, except in some specific scenarios (see the spec, section 14.4.2).
You have to explicitly cast the expression: cmd.ExecuteReader() :> IDataReader then you can remove the type annotation after getReader.
Alternatively you may leave that function returning an SqlDataReader and upcast at the call site:
let foos = getReader :> IDataReader |> Seq.unfold iterFoo
If unfold was a static member of a type with a signature like this one:
type T() =
static member unfold(a, b:IDataReader) = Seq.unfold a b
you would be able to do directly T.unfold(iterFoo, getReader) and it will automatically upcast. That's one of the cases mentioned in the spec.

Can you encapsulate multi case discriminated unions?

I see that you can enforce constructor usage of single-case discriminated unions, can you do the same with multi-case?
for example
type MemberId =
| MemberId of int
| MemberGuid of Guid
I'm currently trying in the fsi like this
val create : int -> T option
val create : Guid -> T option
but I'm guessing like C#, F# won't allow you to overload based on return type for the unwrap:
val value : T -> string
Edit ---------------
MemberId.fsi =
module MemberId
open System
type _T
val createId : int -> _T option
val createGuid : Guid -> _T option
val value : _T -> 'a
MemberId.fs =
module MemberId
open System
type _T =
| Id of int
| MemberGuid of Guid
let createId id = match id with
| x when x>0 -> Some(Id(id))
| _ -> None
let createGuid guid = Some(MemberGuid( guid))
let value (e:_T):int = e
Appears to be pretty close, but the unwrapper doesn't compile and I can't seem to figure out how to write it
TestConsumer MemberIdClient.fs =
module MemberIdClient
open System
open MemberId
let address1 = MemberId.create(-1)
let address2 = MemberId.create(Guid.Empty)
let unwrapped1 =
match address1 with
| MemberId x -> () // compilation error on 'MemberId x'
| _ -> ()
Functions cannot be overloaded, but methods can:
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
static member create id = MemberId id
static member create guid = MemberGuid guid
Indeed there is a way to overload with the output parameter, using some inline tricks:
open System
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
type Value = Value with
static member ($) (Value, d:int ) = function MemberId id -> id | _ -> failwith "Wrong case"
static member ($) (Value, d:Guid) = function MemberGuid guid -> guid | _ -> failwith "Wrong case"
let inline create x : MemberId = Create $ x
let inline value x : 'IntOrGuid = (Value $ Unchecked.defaultof<'IntOrGuid>) x
let a = create 1
let b = create (Guid.NewGuid())
let c:int = value a
let d:Guid = value b
By doing this you can 'overload' functions, even on output parameters.
Anyway the big difference with the single case DU is that now the unwrapper is not 'safe', that's why the unwrapper makes little sense, except in some specif scenarios.
In these cases you may consider other mechanisms to unwrap the values, like exposing functions isX or returning options which may be complemented with an active pattern to unwrap.
Having said that, if you are only interested in 'hiding' the constructors to do some validations, but not hiding the DU you can simply shadow the constructors, here's an example:
open System
type T =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
let a = MemberId 1
let b = MemberGuid (Guid.NewGuid())
let c = MemberId -1
// but you can still pattern match
let printValue = function
| Some (MemberId x) -> sprintf "case 1, value is %A" x
| Some (MemberGuid x) -> sprintf "case 2, value is %A" x
| None -> "No value"
let ra = printValue a // "case 1, value is 1"
let rb = printValue b // "case 2, value is 67b36c20-2..."
let rc = printValue c // "No value"
// and if you want to use an overloaded constructor
type T with
static member Create id = MemberId id
static member Create guid = MemberGuid guid
let d = T.Create 1
let e = T.Create (Guid.NewGuid())
// or using the inline trick
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
let inline create x : T option = Create $ x
let d' = create 1
let e' = create (Guid.NewGuid())
Here's the little bit of code from Gustavo's answer I needed that appears to work all by itself
module MemberId
open System
type MemberId =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)

Resources