F# alternate constructor assigning values to (mutable) let bindings - f#

Suppose I have this class:
type Pet (name:string) as this =
let mutable age = 5
let mutable animal = "dog"
I want to be able to create a new Pet based on some serialized data, which I represent with this record:
type PetData = {
name : string
age : int
animal : string
}
(TLDR: I can't figure out the syntax to make a constructor that'll take a PetData to populate the let bindings. My various attempts follow.)
So I make a new Pet constructor that'll assign values to the let bindings. I try using the class initializer syntax:
new (data:PetData) =
Pet(name,
age = data.age,
animal = data.animal
)
Hmm, nope: No accessible member or object constructor named 'Pet' takes 1 arguments. The named argument 'age' doesn't correspond to any argument or settable return property for any overload.
I check to make sure I've got all the syntax: no missing commas, correct "assignment" (cough) operator, correct indentation.
Okay the, I'll try the record initializer syntax.
new (data:PetData) =
{
name = data.name;
age = data.age;
animal = data.name
}
Error: The type 'Pet' does not contain a field 'name'
Okay, so I need to call the main constructor. I guess there are probably two places I can put it, so let's try both:
new (data:PetData) =
{
Pet(data.name);
age = data.age;
animal = data.name
}
Nope: Invalid object, sequence or record expression
new (data:PetData) =
Pet(data.name)
{
age = data.age;
animal = data.name
}
And nope: This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.
I didn't want to have to do this, but maybe since the fields are mutable anyway, I can just assign values to the object after initializing it:
new (data:PetData) =
let p = Pet(data.name)
p.age <- data.age
p.animal <- data.animal
p
Type constraint mismatch. The type Pet is not compatible with type PetData The type 'Pet' is not compatible with the type 'PetData'
Lol, what??
Okay, let's try this:
let assign(data:PetData) =
this.age <- data.age
this.animal <- data.animal
new (data:PetData) =
let p = Pet(data.name)
p.assign(data)
p
The field, constructor or member 'assign' is not defined
Right, so it can't access let bindings from outside.
Let's try a member then:
new (data:PetData) =
let p = Pet(data.name)
p.Assign(data)
p
member x.Assign(data:PetData) =
this.age <- data.age
this.animal <- data.animal
This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.
Okay... let's try this whole thing differently then, using explicit fields:
type Pet =
[<DefaultValue>]val mutable private age : int
[<DefaultValue>]val mutable private animal : string
val private name : string
new(name:string) =
{ name = name }
new(data:PetData) =
{
name = data.name;
age = data.age;
animal = data.animal
}
Extraneous fields have been given values
And that's when I punch my elderly cat in the face.
Any other ideas? These error messages are throwing me off. I can't even find half of them on Google.

You could do this.
type Pet =
val mutable private age : int
val mutable private animal : string
val private name : string
new (name:string) =
{
name = name;
age = 5; // or age = Unchecked.defaultof<_>;
animal = "dog"; // or animal = Unchecked.defaultof<_>;
}
new (data:PetData) =
{
name = data.name;
age = data.age;
animal = data.animal;
}
F# has its own style which looks like this.
type Pet(name:string, age:int, animal:string) =
let mutable age = age
let mutable animal = animal
new (name:string) =
Pet(name, 5, "dog")
new (data:PetData) =
Pet(data.name, data.age, data.animal)
Edit
Added an event used in do per comment request.
type Pet(name:string, age:int, animal:string, start:IEvent<string>) =
let mutable age = age
let mutable animal = animal
// all three constructors will call this code.
do start.Add (fun _ -> printf "Pet was started")
new (name:string, start:IEvent<_>) =
// an example of different logic per constructor
// this is called before the `do` code.
let e = start |> Event.map (fun x -> x + " from 'name constructor'")
Pet(name, 5, "dog", e)
new (data:PetData, start:IEvent<_>) =
Pet(data.name, data.age, data.animal, start)

Let bindings in a type are private and there's not much you could do about that. As such you cannot use Named Arguments. By creating properties you can do it like so, but not from inside the Pet type:
type Pet (name:string) =
let mutable age = 5
let mutable animal = "dog"
member x.Age with get () = age and set v = age <- v
member x.Animal with get () = animal and set v = animal <- v
type PetData = {
name : string
age : int
animal : string
}
with
member x.ToPet =
new Pet (x.name, Age = x.age, Animal = x.animal)
The other option would be to create a more general constructor like Gradbot suggested, either accepting a PetData object directly or all three parameters.

Related

How to instatiate a 'typed' Map?

I created a custom type which is implementing a Map.
type School = Map<int, string list>
I tried now various ways on how to instatiate that type but it always fails.
With attempt Nr.1 I thought maybe one can 'dot' the class (somehow).
let xyz = School.Map.empty;;
or
let kgse = School.empty;;
//The type 'Map<Key,Value>' does not define the field, constructor or member 'empty'.
Attempt Nr.2 was my hope that f# knows if I create a map which has the same structure of the custom type it assigns it automatically.
let xyz =
- Map.empty.
- Add(2, ["Alex"]);;
val xyz: Map<int,string list> = map [(2, ["Alex"])]
This works but it only returns the general Map class.
Finally, I thought maybe I can cast the type.
let xyz =
- School Map.empty.
- Add(2, ["Alex"]);;
This throw me again an error:
Successive arguments should be separated by spaces or tupled, and arguments involving function or method applications should be parenthesized.
F# have pretty neat feature - types with same name can extend each other. For example System.Collection.Generic have type EqualityComparer with static property Default, which return adequate comparer for given generic type, but it doesn't fit for collections, because they would be compared be reference, instead of by value.
In C# you can't write extensions for static class to call EqualityComparer<T>.ForCollection, but you can with F#:
module EqualityComparer =
let ForCollection<'a> = ...
let def = EqaulityComparer.Default
let mine = EqualityComparer.ForCollection
As you can see, we extended static class with module. This is 2 distinct types with same name and we can use methods and properties from both.
Same happens with Map class and Map module. You've created alias for type which can be instantiated but haven't for helper module. What you need to do is create type abbreviation for module
type School = Map<int, string list>
module School = Map
School.empty
Part of the problem here is that School, as you've defined it, is a type abbreviation, not a distinct type of its own. This means that it is simply another name for Map<int, string list>. That's a good light-weight approach, and still allows you to create your own School.empty value, if you want:
module School =
let empty : School = Map.empty
let xyz = School.empty.Add(2, ["Alex"])
If, on the other hand, you actually want School to be a real type, you should consider defining it as a record or discriminated union instead:
type School =
private MkSchool of Map<int, string list> with
member this.Add(key, values) =
let (MkSchool map) = this
MkSchool (map.Add(key, values))
module School =
let empty = MkSchool Map.empty
let xyz = School.empty.Add(2, ["Alex"])
Add a School type annotation to xyz:
let xyz : School = Map.empty.Add(2, ["Alex"])
Using dotnet fsi:
> type School = Map<int, string list>
-
- let xyz : School = Map.empty.Add(2, ["Alex"]);;
type School = Map<int,string list>
val xyz : School = map [(2, ["Alex"])]
You can also create functions to return School and then use them as follows:
Again, in dotnet fsi:
> let makeSchool s : School = Map.empty.Add s
-
- let addStudent (school: School) student : School = school.Add student
-
- let xyz' = makeSchool (3, ["Betty"])
-
- let newStudent = (4, ["Charles"])
-
- let schoolWithNewStudentAdded = addStudent xyz' newStudent
- ;;
val makeSchool : int * string list -> School
val addStudent : school:School -> int * string list -> School
val xyz' : School = map [(3, ["Betty"])]
val newStudent : int * string list = (4, ["Charles"])
val schoolWithNewStudentAdded : School =
map [(3, ["Betty"]); (4, ["Charles"])]

How to deserialise a missing property to an empty list in an F# record instead of null using Json.Net

Consider an F# record that contains a list value such as this:
type MyRecord = {
Name: string
SomeList: string list
}
Using Netwonsoft.Json.JsonConvert to deserialise JSON to this record when the JSON does not contain a property for the list value Values of the record will lead to the deserialised record having a null value for the list instead of an empty list [].
That is,
open Newtonsoft.Json
JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Some name"}""" ) |> printfn "%A"
// Gives: { Name = "Some name"; SomeList = null; }
How can you deserialise using Netwonsoft.Json so that the list is initialised to an empty list? For example:
{ Name = "Some name"; SomeList = []; }
You can do this with a custom contract resolver such as the following:
type ParameterizedConstructorInitializingContractResolver() =
inherit DefaultContractResolver()
// List is a module not a static class so it's a little inconvenient to access via reflection. Use this wrapper instead.
static member EmptyList<'T>() = List.empty<'T>
override __.CreatePropertyFromConstructorParameter(matchingMemberProperty : JsonProperty, parameterInfo : ParameterInfo) =
let property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo)
if (not (matchingMemberProperty = null) && property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() = typedefof<_ list>) then
let genericMethod = typeof<ParameterizedConstructorInitializingContractResolver>.GetMethod("EmptyList", BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Static)
let concreteMethod = genericMethod.MakeGenericMethod(property.PropertyType.GetGenericArguments())
let defaultValue = concreteMethod.Invoke(null, null)
property.DefaultValue <- defaultValue
property.DefaultValueHandling <- new System.Nullable<DefaultValueHandling>(DefaultValueHandling.Populate)
matchingMemberProperty.DefaultValue <- defaultValue
matchingMemberProperty.DefaultValueHandling <- new System.Nullable<DefaultValueHandling>(DefaultValueHandling.Populate)
property
And then use it as follows:
let settings = JsonSerializerSettings(ContractResolver = new ParameterizedConstructorInitializingContractResolver())
let myrecord1 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Missing SomeList"}""", settings )
let myrecord2 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "Populated SomeList", "SomeList" : ["a", "b", "c"]}""", settings )
let myrecord3 = JsonConvert.DeserializeObject<MyRecord>("""{ "Name": "null SomeList", "SomeList" : null}""", settings )
Notes:
The contract resolver works for any object that is deserialized via a parameterized constructor, which includes, but is not limited to, f# records. If any such object has a constructor argument with type T list for any T then the value will default to List.empty<T> when missing or null.
The contract resolver reuses the same instance of the default value List.empty<T> for all deserialized objects, which is fine here since f# lists are immutable (and List.empty<T> seems to be a singleton anyway). The same approach would not work for providing a default value for mutable collections as a constructor argument.
You may want to cache the contract resolver for best performance.
The constructor parameter must have the same name (modulo case) as the corresponding property.
Demo fiddle here.

F# class reordering Visual Studio

Im building a sample application where my types hierarchy isnt working with types ordering in Visual Studio. No matter what way i try to arrange the files ( up , down ) I cannot get all the classes be defined.
So in the order they are in f# project
type Artist() =
let mutable artistId = 0
let mutable name = String.Empty
member x.ArtistId
with get() = artistId
and set (value) = artistId <- value
member x.Name
with get() = name
and set ( value ) = name <- value
type Genre() =
let mutable name = String.Empty
let mutable genreId = 0
let mutable description = String.Empty
let mutable albums = [new Album()]
member x.Name
with get() = name
and set (value) = name <- value
member x.GenreId
with get() = genreId
and set ( value ) = genreId <- value
member x.Description
with get() = description
and set ( value ) = description <- value
member x.Albums
with get() = albums
and set ( value ) = albums <- value
and Album() =
let mutable title = String.Empty
let mutable genre = new Genre()
let mutable albumId = 0
let mutable genreId = 0
let mutable artistId = 0
let mutable price : decimal = Decimal.Zero
let mutable albumArtUrl = String.Empty
let mutable artist = new Artist()
member x.Title
with get() = title
and set (value) = title <- value
member x.Genre
with get() = genre
and set (value) = genre <- value
member x.AlbumId
with get() = albumId
and set ( value ) = albumId <- value
member x.GenreId
with get() = genreId
and set ( value ) = genreId <- value
member x.ArtistId
with get() = artistId
and set ( value ) = artistId <- value
member x.Price
with get() = price
and set ( value ) = price <- value
member x.AlbumArtUrl
with get() = albumArtUrl
and set ( value ) = albumArtUrl <- value
member x.Artist
with get() = artist
and set ( value ) = artist <- value
So in above case i get the error "Album" is not defined.
Is there a way to solve this ?. Or i just have to rethink the whole of the hierarchy structure for my types?
If you need to define two types that are mutually recursive (meaning that they can both refer to each other), then you need to place them in a single file and use type ... and ... syntax.
In your example, this means that Genre and Album need to be defined like this:
// Start a definition block using 'type' as normal
type Genre() =
let mutable name = String.Empty
let mutable albums = [new Album()]
member x.Name
with get() = name
and set (value) = name <- value
member x.Albums
with get() = albums
and set ( value ) = albums <- value
// Continue single type definition block using 'and'
and Album() =
let mutable genre = new Genre()
let mutable albumId = 0
let mutable artist = new Artist()
member x.Genre
with get() = genre
and set (value) = genre <- value
member x.AlbumId
with get() = albumId
and set ( value ) = albumId <- value
member x.Artist
with get() = artist
and set ( value ) = artist <- value
However, your example is using F# in a very C#-style, so the code does not really look very elegant and it may not give you many of the benefits of functional programming.
If I wanted to represent a structure that you're using, then I probably wouldn't add reference to the genre into the Album type. When you place a list of albums inside a Genre, you will always be able to recover the genre when you process the data structure (i.e. to turn it into some other structure, maybe an F# record, that can be passed to data-binding). The beneift of F# is that it lets you write the domain on a few lines, but that works only for functional types.
Using discriminated unions with a single case, you can write:
// Type aliases to make code more readable
type Name = string
type AlbumID = int
// Simple type definitions to represent the domain
type Artist = Artist of Name
type Album = Album of AlbumID * Artist
type Genre = Genre of Name * Album list

Overloading constructor without initialization

I'm writing a generic class that has two constructors: the first one initializes every field, the second (parameter-less) should not initialize anything.
The only way I found to achieve this is calling the main constructor with "empty" arguments, i.e. Guid.Empty and null. Besides not looking good functional style to my untrained eyes, this means that I have to put a a' : null constraint on the second parameter, which I don't want:
type Container<'a when 'a : null>(id : Guid, content : 'a) =
let mutable _id = id
let mutable _content = content
new() = Container<'a>(Guid.Empty, null)
member this.Id
with get() = _id
and set(value) = _id <- value
member this.Content
with get() = _content
and set(value) = _content <- value
I see two ways to solve this:
use something like the default c# keyword instead of null (does such a thing exist in F#?)
use a different syntax to specify constructors and private fields (how?)
What is the best way to implement this class?
The F# analog to default is Unchecked.default<_>. It is also possible to use explicit fields which you don't initialize:
type Container<'a>() =
[<DefaultValue>]
val mutable _id : Guid
[<DefaultValue>]
val mutable _content : 'a
new (id, content) as this =
new Container<'a>() then
this._id <- id
this._content <- content
However, in general, your overall approach is somewhat unidiomatic for F#. Typically you'd use a simple record type (perhaps with a static method to create uninitialized containers, although this seems to have questionable benefit):
type 'a Container = { mutable id : Guid; mutable content : 'a } with
static member CreateEmpty() = { id = Guid.Empty; content = Unchecked.defaultof<_> }
In many situations, you could even use an immutable record type, and then use record update statements to generate new records with updated values:
type 'a Container = { id : Guid; content : 'a }
[<GeneralizableValue>]
let emptyContainer<'a> : 'a Container =
{ id = Guid.Empty;
content = Unchecked.defaultof<_> }
let someOtherContainer = { emptyContainer with content = 12 }
If the type will be used from languages other than F#, the following provides a natural interface in F#, and C#, for example.
type Container<'a>(?id : Guid, ?content : 'a) =
let orDefault value = defaultArg value Unchecked.defaultof<_>
let mutable _id = id |> orDefault
let mutable _content = content |> orDefault
new() = Container(?id = None, ?content = None)
new(id : Guid, content : 'a) = Container<_>(?id = Some id, ?content = Some content)
member this.Id
with get() = _id
and set(value) = _id <- value
member this.Content
with get() = _content
and set(value) = _content <- value
If it will only be used from F#, you can omit the following constructor overloads
new(id : Guid, content : 'a) = Container<_>(?id = Some id, ?content = Some content)
new() = Container()
because the overload accepting optional args handles both these cases equally well in F#.

Using NoRM to access MongoDB from F#

Testing out NoRM https://github.com/atheken/NoRM from F# and trying to find a nice way to use it. Here is the basic C#:
class products
{
public ObjectId _id { get; set; }
public string name { get; set; }
}
using (var c = Mongo.Create("mongodb://127.0.0.1:27017/test"))
{
var col = c.GetCollection<products>();
var res = col.Find();
Console.WriteLine(res.Count().ToString());
}
This works OK but here is how I access it from F#:
type products() =
inherit System.Object()
let mutable id = new ObjectId()
let mutable _name = ""
member x._id with get() = id and set(v) = id <- v
member x.name with get() = _name and set(v) = _name <- v
Is there an easier way to create a class or type to pass to a generic method?
Here is how it is called:
use db = Mongo.Create("mongodb://127.0.0.1:27017/test")
let col = db.GetCollection<products>()
let count = col.Find() |> Seq.length
printfn "%d" count
Have you tried a record type?
type products = {
mutable _id : ObjectId
mutable name : string
}
I don't know if it works, but records are often good when you just need a class that is basically 'a set of fields'.
Just out of curiosity, you can try adding a parameter-less constructor to a record. This is definitely a hack - in fact, it is using a bug in the F# compiler - but it may work:
type Products =
{ mutable _id : ObjectId
mutable name : string }
// Horrible hack: Add member that looks like constructor
member x.``.ctor``() = ()
The member declaration adds a member with a special .NET name that is used for constructors, so .NET thinks it is a constructor. I'd be very careful about using this, but it may work in your scenario, because the member appears as a constructor via Reflection.
If this is the only way to get succinct type declaration that works with libraries like MongoDB, then it will hopefuly motivate the F# team to solve the problem in the future version of the language (e.g. I could easily imagine some special attribute that would force F# compiler to add parameterless constructor).
Here is a pretty light way to define a class close to your C# definition: it has a default constructor but uses public fields instead of getters and setters which might be a problem (I don't know).
type products =
val mutable _id: ObjectId
val mutable name: string
new() = {_id = ObjectId() ; name = ""}
or, if you can use default values for your fields (in this case, all null):
type products() =
[<DefaultValue>] val mutable _id: ObjectId
[<DefaultValue>] val mutable name: string

Resources