Is it possible to set sub-properties on initialization in F#? [duplicate] - f#

This question already has answers here:
C# object initialization syntax in F#
(3 answers)
Closed 6 years ago.
I recently found (see near the end of this page) that it's possible to set properties on initialization, as in the last line of the following. This is very concise:
type Account() =
let mutable balance = 0.0
member this.Balance
with get() = balance
and set(value) = balance <- value
let account1 = new Account(Balance = 1543.33)
Is there a way to set sub-properties (i.e. properties of properties) in a similarly concise way, without overwriting them completely?
For example, I would like to write something along these lines:
type Person() =
let mutable name = ""
let mutable someProperty = ""
member this.Name
with get() = name
and set(value) = name <- value
member this.SomeProperty
with get() = someProperty
and set(value) = someProperty <- value
type Account() =
let mutable balance = 0.0
let mutable person = new Person(SomeProperty = "created by an account")
member this.Person
with get() = person
and set(value) = person <- value
member this.Balance
with get() = balance
and set(value) = balance <- value
let account1 = new Account(Balance = 1543.33, Person.Name = "John Smith")
However, the last line produces a compile error which doesn't make complete sense: Named arguments must appear after all other arguments.
Please note this is actually for interop with a C# library, so I can't necessarily construct a new object for the property. I wouldn't use mutable properties like this in F# if at all possible.

Yes, you can do this.
Try the following:
let account1 = new Account(Balance = 1543.33, Person = Person(Name = "John Smith"))
Edits following change to posters question:
I'm still not 100% sure if I follow correctly, but a solution could be the following. It doesn't feel particularly functional, but given this is meant to interact with C# classes I don't see that as an issue:
type Account() =
let mutable balance = 0.0
static let mutable person = new Person(SomeProperty = "created by an account")
member this.Person
with get() = person
and set(value) = person <- value
member this.Balance
with get() = balance
and set(value) = balance <- value
static member GetPerson = person
let account2 = new Account(Balance = 1543.33, Person = Person (Name = "John Smith", SomeProperty = Account.GetPerson.SomeProperty))

Related

How can I create a CustomEvent with detail object in Fable?

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.

DRY self-replicating type

Is there a succint way to express self-replicating types in F#? — That is, without repeating oneself.
// Manual self-replication
type Foo (par1 : Type1, par2 : Type2, par3 : Type3, par4 : Type4) =
let unique = new UniqueState() // unique for every instance of Foo
member this.SelfReplicate =
new Foo(par1, par2, par3, par4) // repeating myself
let a = new Foo(x, y, z, w)
let b = a.SelfReplicate
Attempt with manually injected self-replicator:
// Semi-automagic self-replication
type Foo' (par1 : Type1, par2 : Type2, par3 : Type3, par4 : Type4, replicate : unit -> Foo') =
let unique = new UniqueState() // unique for every instance of Foo'
member this.SelfReplicate = replicate() // not repeating myself
let rec foo' () = new Foo'(x, y, z, w, foo')
let a = foo'()
let b = a.SelfReplicate
I'm not sure how this can be any more succint without compiler magic. It just seems like there should be a way to capture the current arguments and type without repeating them syntactically.
You could define a type WithUnique<'T> which is a wrapper over a value of type 'T and adds a unique value to this. You may need to think about how you want the equality testing on those types to work - if you use record (as I do below), then two instances with different unique value will not be equal:
let rnd = System.Random()
let uniqueState() = rnd.Next()
type WithUnique<'T> =
{ Value : 'T; Unique : int }
static member Create(v) : WithUnique<'T> =
{ Value = v; Unique = uniqueState() }
member x.Replicate() =
{ Value = x.Value; Unique = uniqueState() }
The value of 'T is just one type, but this can be a tuple (or a record) if you need to wrap multiple things:
let wu1 = WithUnique.Create( (10, "hi") )
let wu2 = wu1.Replicate()
Given the above, wu1=wu2 will be false.

F#: how to have the variables defined above a XUnit test function actually initialized when running the test?

Following up another question: F#: Why those two collections are not equal? the example below shows that when running Open an account... test id and contact are not initialized.
If there were functions returning the same values and called in the test body it would work though.
I am wondering why this is case, and if there is anything I can do to have those variables properly initialized when the test is running.
let id = Guid.Empty
let contact = {
Name = {
FirstName = "Marcel"
MiddleInitial = None
LastName = "Patulacci"
}
DateOfBith = new DateTime(1850, 12, 25)
Address = {
Address1 = "41 av 8 Mai 1945"
Address2 = None
City = "Sarcelles"
State = None
Zip = "95200"
}
PhoneNumber = {
DialOutCode = 33
LocalNumber = "766030703"
}
Email = "marcel.patulacci#outlook.com"
}
[<Fact>]
let ``Open an account...``() =
let event = Event.AccountOpened({
AccountId = id
Contact = contact
})
let a = [event]
let b = seq { yield event }
Assert.Equal(a, b)
This is down to how F# modules are implemented in .NET IL. Modules are compiled into static classes, and module-defined values are initialized in the class's static constructor. But because of the way XUnit loads tests, the static constructor is not run.
A possible way to circumvent this is to use a class instead of a module, as XUnit does run instance constructors. let functions in a class are compiled to private methods, so the tests are recognized by XUnit without having to switch to member syntax.
type MyTests() =
let id = Guid.Empty
let contact = // ...
[<Fact>]
let ``Open an account...``() =
// ...

Subtracting Records from a Set using case-insensitive comparison

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

Extension methods for F# tuples

Is it possible to write extension methods for F# tuples? For example, to add instance methods .Item1 and .Item2 (like System.Tuple) which are equivalent to calling fst and snd for 2-tuples?
The System.Tuple<'T1, 'T2> type that internally represents (2-element) tuples in F# actually already has properties Item1 and Item2, but these are hidden by the F# compiler. An obvious method to add extension members to a tuple does not do the trick, so I would not expect this to work (but there may be some workaround I'm not aware of).
Generally, I think pattern matching is preferable to members such as Item1, Item2 etc. (and C# 3.0 programmers often ask for pattern matching support when working with tuples :-)).
The reason is that pattern matching forces you to name things. Compare these two code snippets:
let (width, height) = tuple
width * height
and a version using properties:
tuple.Item1 * tuple.Item2
The second is a bit shorter, but definitely less readable.
Not perfect but I'm using this. (I borrowed original code from http://www.fssnip.net/6V and added small modification.)
[<AutoOpen>]
module TupleExtensions =
type System.Tuple with
static member Item1(t) = let (x,_) = t in x
static member Item1(t) = let (x,_,_) = t in x
static member Item1(t) = let (x,_,_,_) = t in x
static member Item1(t) = let (x,_,_,_,_) = t in x
static member Item1(t) = let (x,_,_,_,_,_) = t in x
static member Item1(t) = let (x,_,_,_,_,_,_) = t in x
static member Item2(t) = let (_,x) = t in x
static member Item2(t) = let (_,x,_) = t in x
static member Item2(t) = let (_,x,_,_) = t in x
static member Item2(t) = let (_,x,_,_,_) = t in x
static member Item2(t) = let (_,x,_,_,_,_) = t in x
static member Item2(t) = let (_,x,_,_,_,_,_) = t in x
static member Item3(t) = let (_,_,x) = t in x
static member Item3(t) = let (_,_,x,_) = t in x
static member Item3(t) = let (_,_,x,_,_) = t in x
static member Item3(t) = let (_,_,x,_,_,_) = t in x
static member Item3(t) = let (_,_,x,_,_,_,_) = t in x
static member Item4(t) = let (_,_,_,x) = t in x
static member Item4(t) = let (_,_,_,x,_) = t in x
static member Item4(t) = let (_,_,_,x,_,_) = t in x
static member Item4(t) = let (_,_,_,x,_,_,_) = t in x
static member Item5(t) = let (_,_,_,_,x) = t in x
static member Item5(t) = let (_,_,_,_,x,_) = t in x
static member Item5(t) = let (_,_,_,_,x,_,_) = t in x
static member Item6(t) = let (_,_,_,_,_,x) = t in x
static member Item6(t) = let (_,_,_,_,_,x,_) = t in x
static member Item7(t) = let (_,_,_,_,_,_,x) = t in x
How to use it:
let t = (1, 2, 3)
let item1 = Tuple.Item1(t)
Tuple.Item1 defined here has advantage over fst: It is polymorphic for number of items. Once we write function which uses n tuple using these extension methods, we can extend it for n+1 tuple without modifying function body. Instead we have to modify argument type declaration. It is more effortless.
I think, what you're asking is not very functional way. You can make your own type with instance methods, but at the same time you are losing many aspects of functional programming, e.g. pattern matching.
Other than that, a DU seems to be the way to go:
type MyTuple<'T, 'U> =
| MyTuple of 'T * 'U
with
member this.MyItem1 = match this with | MyTuple(x,y) -> x
member this.MyItem2 = match this with | MyTuple(x,y) -> y
let x = MyTuple(42, "foo")
let y1 = x.MyItem1 // 42
let y2 = x.MyItem2 // "foo"
As #Tomas Petricek noted, you can't name the properties Item1 and Item2 since they already exist in System.Tuple<'T1, 'T2>. Attempting to do that will cause an error:
error FS2014: A problem occurred writing the binary [filename]: Error in pass2 for type [...], error: Error in pass2 for type MyTuple`2, error: duplicate entry 'Item1' in property table
You could also use the fst and snd functions to get the values you want (and obviously write your own for third, fourth, etc. if you really wanted to).
The workaround is to use C# style extension definitions.
This will work just fine:
open System.Runtime.CompilerServices
[<Extension>]
type TupleExtensions () =
[<Extension>] static member First((a,b)) = a
[<Extension>] static member First((a,b,c)) = a
let x = (1,2).First()
let y = (1,2,3).First()
But I agree in that it's not a good idea to access the elements of a tuple through methods, pattern matching is the best way.

Resources