(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.
Related
I am REALLY new to F#, so I might have used the wrong terminology here. Please feel free to correct me if I am wrong, I would really appreciate it! Anyways, on to the question
I have a record that I have defined as so:
type EventSource = {
SourceName: string
Address: string
ParseDocument: HtmlDocument -> Event seq }
And I have created an instance of that record like so:
let lofSource = {
SourceName = "LOF"
Address = "https://lof.dk/syd/kurser"
ParseDocument = fun document ->
document.Descendants ["div"]
|> Seq.filter (fun d -> d.HasClass("item"))
|> Seq.map (
fun e ->
let linkElement
= e.Descendants (fun j -> j.HasClass "item-title")
|> Seq.head
|> (fun y -> y.Descendants ["a"])
|> Seq.map (fun fa -> fa.Attribute "href")
|> Seq.head
{
Title = e.AttributeValue "data-holdnavn"
Link = linkElement.Value()
Status = e.AttributeValue "data-status"
Image = Address //Here!
City = e.AttributeValue "data-bynavn"
Date = System.DateTime.ParseExact(e.AttributeValue("data-datosort"), "yyyyMMdd", null);
PostalCode = e.AttributeValue("data-postnr")})
}
On the line where I am trying to assign a value the Image member, It tells me that the value or constructor 'Address' is not defined.
I have tried using a self-identifier on the instantiation of the record and then trying to access Address like
this.Address
but it tells me that 'this' is not defined. I am guessing I am missing something quite fundamental here, can anyone help me? Is what I am trying to do nonsensical?
You can't do this with records. See: Reference a record from within itself during construction
You can do it with another binding (I couldn't get your code to compile and have simplified it):
type EventSource = {
SourceName: string
Address: string
ParseDocument: string -> string}
let lofSource =
let helloThere = "General Kenobi"
{
SourceName = "LOF"
Address = foo
ParseDocument = fun document ->
foo
}
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 ->
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"
How could nested pattern matching, such as the following example, be re-written so that None is specified only once? I think the Maybe monad solves this problem. Is there something similar in the F# core library? Or, is there an alternative approach?
match a with
| Some b ->
let c = b.SomeProperty
match c with
| Some d ->
let e = d.SomeProperty
//and so on...
| None -> ()
| None -> ()
you can solve this using built-in capabilities: Option.bind
type A =
member this.X : B option = Unchecked.defaultof<_>
and B =
member this.Y : С option = Unchecked.defaultof<_>
and С =
member this.Z : string option = Unchecked.defaultof<_>
let a : A = Unchecked.defaultof<_>
let v =
match
a.X
|> Option.bind (fun v -> v.Y)
|> Option.bind (fun v -> v.Z) with
| Some s -> s
| None -> "<none>"
Frankly, I doubt that introducing full-fledged 'maybe' implementation (via computation expressions) here can shorten the code.
EDIT: Dream mode - on
I think that version with Option.bind can be made smaller if F# has more lightweight syntax for the special case: lambda that refer to some member of its argument:
"123" |> fun s -> s.Length // current version
"123" |> #.Length // hypothetical syntax
This is how the sample can be rewritten in Nemerle that already has such capabilities:
using System;
using Nemerle.Utility; // for Accessor macro : generates property for given field
variant Option[T]
{
| Some {value : T}
| None
}
module OptionExtensions
{
public Bind[T, U](this o : Option[T], f : T -> Option[U]) : Option[U]
{
match(o)
{
| Option.Some(value) => f(value)
| Option.None => Option.None()
}
}
}
[Record] // Record macro: checks existing fields and creates constructor for its initialization
class A
{
[Accessor]
value : Option[A];
}
def print(_)
{
// shortened syntax for functions with body -> match over arguments
| Option.Some(_) => Console.WriteLine("value");
| Option.None => Console.WriteLine("none");
}
def x = A(Option.Some(A(Option.Some(A(Option.None())))));
print(x.Value.Bind(_.Value)); // "value"
print(x.Value.Bind(_.Value).Bind(_.Value)); // "none"
I like desco's answer; one should always favor built-in constructs. But FWIW, here's what a workflow version might look like (if I understand the problem correctly):
type CE () =
member this.Bind (v,f) =
match v with
| Some(x) -> f x
| None -> None
member this.Return v = v
type A (p:A option) =
member this.P
with get() = p
let f (aIn:A option) = CE () {
let! a = aIn
let! b = a.P
let! c = b.P
return c.P }
let x = f (Some(A(None)))
let y = f (Some(A(Some(A(Some(A(Some(A(None)))))))))
printfn "Your breakpoint here."
I don't suggest this, but you can also solve it with exception handling:
try
<code that just keeps dotting into option.Value with impunity>
with
| :? System.NullReferenceException -> "None"
I just wanted to point out the rough equivalence of exception-handling to the Maybe/Either monads or Option.bind. Typically prefer one of them to throwing and catching exceptions.
Using Option.maybe from FSharpx:
open FSharpx
type Pet = { Name: string; PreviousOwner: option<string> }
type Person = { Name: string; Pet: option<Pet> }
let pers = { Name = "Bob"; Pet = Some {Name = "Mr Burns"; PreviousOwner = Some "Susan"} }
Option.maybe {
let! pet = pers.Pet
let! prevOwner = pet.PreviousOwner
do printfn "%s was the previous owner of %s." prevOwner pet.Name
}
Output:
Susan was the previous owner of Mr Burns.
But, e.g. with this person instead there is just no output:
let pers = { Name = "Bob"; Pet = None }
Why is t.b evaluated on every call? And is there any way how to make it evaluate only once?
type test =
{ a: float }
member x.b =
printfn "oh no"
x.a * 2.
let t = { a = 1. }
t.b
t.b
An alternative version of Brian's answer that will evaluate b at most once, but won't evaluate it at all if B is never used
type Test(a:float) =
// constructor
let b = lazy
printfn "oh no"
a * 2.
// properties
member this.A = a
member this.B = b.Value
It's a property; you're basically calling the get_b() member.
If you want the effect to happen once with the constructor, you could use a class:
type Test(a:float) =
// constructor
let b = // compute it once, store it in a field in the class
printfn "oh no"
a * 2.
// properties
member this.A = a
member this.B = b
In response to your comments in Brian's post, you can fake copy-and-update record expressions using optional/named args. For example:
type Person(?person:Person, ?name, ?age) =
let getExplicitOrCopiedArg arg argName copy =
match arg, person with
| Some(value), _ -> value
| None, Some(p) -> copy(p)
| None, None -> nullArg argName
let name = getExplicitOrCopiedArg name "name" (fun p -> p.Name)
let age = getExplicitOrCopiedArg age "age" (fun p -> p.Age)
member x.Name = name
member x.Age = age
let bill = new Person(name = "Bill", age = 20)
let olderBill = new Person(bill, age = 25)
printfn "Name: %s, Age: %d" bill.Name bill.Age
printfn "Name: %s, Age: %d" olderBill.Name olderBill.Age
The previous responses suggest switching to a class, instead of using a record. If you want to stay with records (for their simple syntax and immutability), you can take this approach:
type test =
{ a : float
b : float }
static member initialize (t: test) =
{ t with b = t.a * 2. }
This is useful if the instance of test is created by another library (like a data provider from a web service or database). With this approach, you must remember to pass any instance of test that you receive from that API through the initialize function before using it in your code.