I wonder if there is a better way of implementing a function that accepts records and modify them.
So I have entities of two types, both have corresponding files on the disk:
type Picture = { Artist : string; File : string }
type Book = { Author : string; File : string }
I want generic function that can copy both pictures and books. In the OOP world I would probably create common interface IArtefact { File : string }, implement it in both records and then create Move method that works on it. Something like:
let move<'a:IArtefact>(a : 'a) (path : string) =
File.Move(a.File, path)
{ a with File = path }
However I suppose that F# does not support such concept. What is the F# way of doing so?
This is possible, why wouldn't it be ;)
type IArtefact =
abstract File: string
type Picture =
{ Artist : string; File : string }
interface IArtefact with
member this.File = this.File
let p = { Artist = "test"; File = "test2" }
(p :> IArtefact).File
Edit: If you want to handle updates:
type IArtefact =
abstract File: string
abstract WithFile: string -> IArtefact
type Picture =
{ Artist : string; File : string }
interface IArtefact with
member this.File = this.File
member this.WithFile(file) = { this with File = file } :> IArtefact
While there is no generic way of change-copying records, there is one for moving anything that has a File:
let inline move from where : string =
let oldFile = (^T : (member File : string) from)
do() // move here
where
type Picture = { Artist: string; File: string }
type Book = { Author: string; File: string }
let p = { Artist = "Vincent van Gogh"; File = "Rooftops" }
let p' = { p with File = move p "The Potato Eaters" }
let b = { Author = "Don Syme"; File = "Generics in CLR" }
let b' = { b with File = move b "Expert F#" }
This could then be expanded to move anything that knows how to be moved:
let inline move' a where =
let oldFile = (^T : (member File : string) a)
do() // move here
(^T: (member moved : string -> ^T) a, where)
type Picture' =
{ Artist: string; File: string } with
member this.moved where = { this with File = where }
type Book' =
{ Author: string; File: string } with
member this.moved where = { this with File = where }
let p2 = { Artist = "Vincent van Gogh"; File = "Rooftops" }
let p2' = move' p2 "The Potato Eaters"
let b2 = { Author = "Don Syme"; File = "Generics in CLR" }
let b2' = move' b2 "Expert F#"
Related
In Javascript it is possible to create a CustomEvent and include a second argument containing detail data to pass when the event is dispatched. For example:
let evt = new CustomEvent("hello", {
detail: { name: "John" }
});
elem.dispatchEvent(evt);
With Fable I can create a CustomEvent without the detail data as follows:
let evt = CustomEvent.Create "hello"
elem.dispatchEvent evt
The definition in Browser.Events is as follows:
type [<AllowNullLiteral>] CustomEventType =
[<Emit("new $0($1...)")>] abstract Create : typeArg: string * ?eventInitDict: CustomEventInit -> CustomEvent
[<Emit("new $0($1...)")>] abstract Create : typeArg: string * ?eventInitDict: CustomEventInit<'T> -> CustomEvent<'T>
But I can't work out how to create a CustomEventInit as I think this is an interface.
let details:CustomEventInit = { detail = {name = "John"}} //ERROR: This type is not a record type
let evt = CustomEvent.Create ("hello", details)
elem.dispatchEvent evt
Any help on how to achieve this would be greatly appreciated!
CustomEventInit is a class type, so you can instantiate it using an object expression:
let details =
let mutable bubbles = true
let mutable cancelable = true
let mutable composed = true
let mutable detail : obj = "John"
{
new CustomEventInit with
member _.bubbles
with set(value) = bubbles <- value
and get() = bubbles
member _.cancelable
with set(value) = cancelable <- value
and get() = cancelable
member _.composed
with set(value) = bubbles <- value
and get() = bubbles
member _.detail
with set(value) = detail <- value
and get() = detail
}
Or you could create a named subclass if you prefer.
I have a type 'Team' which contains another type 'Employee'. I have overridden the ToString() for the type 'Employee'. However, when I do ToString() for the type 'Team', the details from 'Employee' is pretty-printed with the standard ToString() implementation and my overriding logic was never used. Can someone help understand why the override didn't work? Here is the code:
type Employee =
{
name : string
address : string
}
override this.ToString() = sprintf "Hello %s" this.name
type Team =
{
employee1 : Employee
}
with member this.ToTightString =
this.ToString().Replace(" ","")
let employee = { name="Bob"; address="Unknown"; }
let team = {employee1=employee}
printfn "%s" (employee.ToString()) // Override works!
// OUTPUT: Hello Bob
printfn "--------------------"
printf "%s" team.ToTightString // Override doesn't work
// OUTPUT: {employee1={name="Bob";address="Unknown";};}
As #rmunn has said above, the textual representation of a type (say, type1) specified in StructuredFormatDisplay is retained even if one calls ToString() on a type that contains the 'type1' type. Here's an example:
open System.Text.RegularExpressions
[<StructuredFormatDisplay("name=Always Harry address={address}")>]
type Employee =
{
name : string
address : string
}
type AddressContainer =
{
employee: Employee
containerName: string
}
let address1 = { name="Bob"; address="Random City" }
let addressContainer1 = { employee=address1; containerName= "container1"}
printf "%s" (address1.ToString()) // prints "name=Always Harry address=Random City"
printf "%s" (addressContainer1.ToString()) // prints {employee = name=Always Harry address=Random City; containerName = "container1";}
Suppose I have 2 record types
type A = { a: string; parameters: parameter list }
type B = { b: string; parameters: parameter list }
where
type parameter = { name: string; value : string }
How can I write function parameter
let parameter name value entity =
{ entity with parameters = List.append
parameters
[ { name = name; value = value; } ]
}
Such as
let a = { a = "a", parameters = [] } |> parameter "p", "v" // a is a record of type A
let b = { b = "b", parameters = [] } |> parameter "p", "v" // b is record of type B
It is not idiomatic F#, but this can be done using SRTP. I assume you have simplified the use-case for StackOverflow, but if A and B are really not related types, then I think you should revisit your overall program design.
I defined a Parameter type as this:
type Parameter =
{
Name : string
Value : string
}
Now, we need to add a method to types A and B that implement the addition of a parameter:
type A =
{
A : string
Parameters : Parameter list
}
with
member this.AddParameter(p : Parameter) =
{
this with
Parameters =
p :: this.Parameters
}
And...
type B =
{
B : string
Parameters : Parameter list
}
with
member this.AddParameter(p : Parameter) =
{
this with
Parameters =
p :: this.Parameters
}
Then we can write an inline function that calls this method:
let inline addParameter (p : Parameter) (x : ^t) : ^t =
(^t : (member AddParameter : Parameter -> ^t) (x, p))
Here ^t will be replaced with A or B (or whatever) depending on the call-site. The syntax for SRTP isn't great, but it is better in F# 7.
Usage:
let p = { Name = "p"; Value = "abc" }
let a : A =
{ A = "a"; Parameters = [] }
|> addParameter p
printfn $"%A{a}"
let b : B =
{ B = "b"; Parameters = [] }
|> addParameter p
printfn $"%A{b}"
I have a set of records:
type Person =
{
Name : string
Age : int
}
let oldPeople =
set [ { Name = "The Doctor"; Age = 1500 };
{ Name = "Yoda"; Age = 900 } ]
Unlike the hardcoded example above, the set of data actually comes from a data source (over which I have very little control). Now I need to subtract a set of data from another data source. In general, the data in this second source matches, but occasionally there is a difference in captialization:
let peopleWhoAreConfusedAboutTheirAge =
set [ { Name = "THE DOCTOR"; Age = 1500 } ]
When I attempt to subtract the second set from the first, it fails because the string comparison is case sensitive:
let peopleWhoKnowHowOldTheyAre =
oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> =
set [{Name = "The Doctor";
Age = 1500;}; {Name = "Yoda";
Age = 900;}]
Is there a way to perform a case-insensitive comparison for the Name field of the People record?
This is what I've implemented so far, though there may be a better way to do it.
My solution was to override the Equals function on the People record so as to perform a case-insensitive comparison. Set subtraction uses the Equals function to determine if two records match one another. By overriding Equals, I was forced (via warning and error) to override GetHashCode and implement IComparable (as well as set the CustomEquality and CustomComparison attributes):
[<CustomEquality; CustomComparison>]
type Person =
{
Name : string
Age : int
}
member private this._internalId =
this.Name.ToLower() + this.Age.ToString()
interface System.IComparable with
member this.CompareTo obj =
let other : Person = downcast obj
this._internalId.CompareTo( other._internalId )
override this.Equals( other ) =
match other with
| :? Person as other ->
System.String.Compare( this._internalId, other._internalId ) = 0
| _ -> false
override this.GetHashCode() =
this._internalId.GetHashCode()
This, however, seems to do the trick:
let oldPeople =
set [ { Name = "The Doctor"; Age = 1500 };
{ Name = "Yoda"; Age = 900 } ]
let peopleWhoAreConfusedAboutTheirAge =
set [ { Name = "THE DOCTOR"; Age = 1500 } ]
let peopleWhoKnowHowOldTheyAre =
oldPeople - peopleWhoAreConfusedAboutTheirAge
val peopleWhoKnowHowOldTheyAre : Set<Person> = set [{Name = "Yoda";
Age = 900;}]
If you know a better solution (involving less code), please post it rather than comment on this answer. I will happily accept a less verbose, awkward solution.
Here's another approach:
type Name(value) =
member val Value = value
override this.Equals(that) =
match that with
| :? Name as name -> StringComparer.CurrentCultureIgnoreCase.Equals(this.Value, name.Value)
| _ -> false
override this.GetHashCode() =
StringComparer.CurrentCultureIgnoreCase.GetHashCode(this.Value)
type Person =
{
Name: Name
Age: int
}
{Name=Name("John"); Age=21} = {Name=Name("john"); Age=21} //true
Does the compiler create a new location in memory when a record is extended (deep copy?) or does the compiler make the record mutable and modify the value?
For example:
type MyRecord = { A : string
; B : string
}
let record = { A = "A"; B = "B" }
let record = { record with A = "new A" } //copy or overwrite?
Since I am overwriting record does the compiler copy or overwrite? Are there performance concerns either way?
It makes the copy.
Copy-and-update Record expression
*A copy-and-update record expression elaborates as if it were a record expression written as follows:
let v = expr in { field-label1 = expr1 ; … ; field-labeln = exprn; F1 = v.F1; ... ; FM = v.FM }
where F1 ... FM are the fields of R that are not defined in field-initializers and v is a fresh variable.*
This
type T = {
A : string
B : string
}
let x = { A = "a"; B = "b" }
let y = { x with A = "aa" }
is equivalent to this
class T {
public readonly string A;
public readonly string B;
public T(string a, string b) {
A = a;
B = b;
}
}
var x = new T("a", "b");
var y = new T("aa", x.B);