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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With