1
votes

I'm writing Domain Logic for one of the application using F#. I want to control access on fields defined for a type in F# so that I can implement builder pattern and control type instantiation.

Want to do something similar with do in builder pattern in C#/Java.

Kindly suggest.

e.g. let Person = { Name:string Age:number Salary:string }

So I want to make sure person should always be instantiated with Name and Age. But salary should be optional. Instantiation should happen in a builder pattern.

3
If you need to control access, you cannot use a record but should rather create an object (docs.microsoft.com/en-us/dotnet/fsharp/language-reference/…). Given that the Builder pattern is an object-oriented approach, an object would fit better anyway. - dumetrulo
Do you want to use an object oriented approach in F#, or do you want to use a more F#-y functional approach? - Guran

3 Answers

2
votes

You cannot make individual record fields private or internal, but you can make all fields private or internal. This hides all the fields, so you have to expose those that you want to make accessible as members. You can also add static methods to create new values. For example:

type Person = 
  private { 
    name : string
    age : int 
    salary : int option }
  member x.Name = x.name
  member x.Age = x.age
  member x.Salary = x.salary

  static member Create(name:string, age:int) = 
    { name = name; age = age; salary = None }
  member x.WithSalary(s) =
    { x with salary = Some s }

Now you cannot access the fields or create the record directly, but you can use Create and WithSalary:

Person.Create("Joe", 60).WithSalary(10000)
1
votes

You can implement the builder pattern with plain functions. I see no reason to use objects here:

type person =
  private {
    name: string
    age: int
    salary: int option
  }

let name person = person.name
let age person = person.age
let salary person = person.salary

let create name age = 
  { name = name; age = age; salary = None }

let withSalary salary person =
   { person with salary = Some salary }

create "Joe" 60
  |> withSalary 10000
  |> name
1
votes

As pointed out in a comment, it seems like you are trying to use object oriented patterns without objects, which hardly is a good idea.

If you decide to use F# types, you would do it like this:

type Employee = {
    name: string
    age: int
    salary: int option
}

let person1 = {
    name = "Mike",
    age = 42,
    salary = Some 4200
}
let person2 = {
    name = "Sue",
    age = 37,
    salary = None
}

Omitting name or age would give you a compile time error, as would using person1 or person2 without checking whether salary exists.

To implement something resembling a builder pattern, with private data, do something like this:

type Employee = private {
    name: string
    age: int
    salary: int option
}

let name employee = employee.name
let employee name age = { name = name; age = age; salary = None }
let withSalary salary employee = { employee with salary = Some salary }

let mike = employee "Mike" 42 |> withSalary 4200

mike.name  //return name of this employee