how does the compiler treat extending a record - f#

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);

Related

How do I implement a method with a variable number of arguments?

How do I implement a method with a variable number of arguments?
In C#, we can use the params keyword:
public class MyClass
{
public static void UseParams(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
Console.Write(list[i] + " ");
}
Console.WriteLine();
}
}
So how can I do this in F#?
type MyClass() =
member this.SomeMethod(params (args:string array)) = ()
I receive the following error from the code above:
The pattern discriminator 'params' is not defined
You can use ParamArrayAttribute:
type MyClass() =
member this.SomeMethod([<ParamArray>] (args:string array)) = Array.iter (printfn "%s") args
then:
let mc = MyClass()
mc.SomeMethod("a", "b", "c")

F# polymorphic function

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}"

Generic method on record

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#"

Is it possible to force record type in F# when doing an assignment?

To explain I think it is best with an example:
type myRec = {x: string}
type myRec2 = {x: string}
let x = {x = "hello"}
let y(a: myRec) = a.x
y(x);;
y(x);;
--^
error FS0001: This expression was expected to have type
myRec
but here has type
myRec2
So how do I force x to have the type myRec if both myRec and myRec2 has the same signature?
let x = { myRec.x = "hello" }
// or
let x:myRec = { x = "hello" }
// or
let x = { x = "hello" } : myRec
Further details and examples are available in the documentation.
EDIT: Incorporated alternatives from comments.
Yes you can:
let x = { new myRec() with x = "hello" }
use and to assign more fields:
let x = { new myRec3() with x = "hello" and y = "bye" }

pipe record to object param list

Given i have the type:
type NewsMessage(identifier:string, headline:string)
and this record:
type NewsMessageParams = {
identifier:string
headline:string
}
Is there an implicit way to adapt the record into the class constructor?
Something like so:
let newsMessageParmas = {identifier=""; headline=""}
new NewsMessage(newsMessageParams) //this is where i need help
You can do this with tuples using the ||> syntax. I would like to do this with a record.
Something like this would work:
let toClass {identifier = i; headline = h} = NewsMessage(i, h)
// Usage
let newsMessageParams = {identifier = ""; headline = ""}
let newsMessage = newsMessageParams |> toClass
Note that object constructor isn't first-class in F#, so you have more chances of using pipe operators if declaring NewsMessage as discriminated unions:
type NewsMessage = NewsMessage of string * string
let toTuple {identifier = i; headline = h} = i, h
let newsMessageParams = {identifier = ""; headline = ""}
let newsMessage = newsMessageParams |> toTuple |> NewsMessage

Resources