lobsterism
lobsterism

Reputation: 3509

Return a modified version of same type in F#

If I've got a class hierarchy like

type Employee(name) =
  member val name: string = name

type HourlyEmployee(name, rate) =
  inherit Employee(name)
  member val rate: int = rate

type SalariedEmployee(name, salary) =
  inherit Employee(salary)
  member val salary: int = salary

And I want a function that updates the name field in a pure way, how is this possible? A couple failed options:

let changeName(employee: Employee) = 
  // no idea what class this was, so this can only return the base class

let changeName<'a when 'a :> Employee>(employee: 'a) =
  // 'a has no constructor

The closest thing I've come up with is making a virtual Employee.changeName and implementing that on each class. That just seems like a lot of extra work plus it's error-prone since the return type is Employee and has to be upcasted back to the original class.

Seems like there should be a simpler, safer way to do such a task. Is this something where typeclasses are necessary?

Update

Yes I could just make the name field mutable, which is how it is implemented in my code now, but that's what I'm wanting to get away from.

Update 2

A solution I've come up with, that meets type safety and conciseness requirements, would be to define

type Employee<'a> = {name: string; otherStuff: 'a}

and then just use with syntax to change the name. But otherStuff: 'a is obviously ugly and hacky looking code, so I'm still open to better solutions.

Upvotes: 3

Views: 133

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233170

If you're looking for something both pure and idiomatic F#, then you shouldn't be using an inheritance hierarchy in the first place. That's an object-oriented concept.

In F#, you could model Employee like this, using algebraic data types:

type HourlyData = { Name : string; Rate : int }
type SalaryData = { Name : string; Salary : int }

type Employee =
| Hourly of HourlyData
| Salaried of SalaryData

This would enable you to create Employee values like this:

> let he = Hourly { Name = "Bob"; Rate = 100 };;

val he : Employee = Hourly {Name = "Bob";
                            Rate = 100;}

> let se = Salaried { Name = "Jane"; Salary = 10000 };;

val se : Employee = Salaried {Name = "Jane";
                              Salary = 10000;}

You can also define a function to change the name in a pure manner:

let changeName newName = function
    | Hourly h -> Hourly { h with Name = newName }
    | Salaried s -> Salaried { s with Name = newName }

This enables you to change the name of an existing Employee value like this:

> let se' = se |> changeName "Mary";;

val se' : Employee = Salaried {Name = "Mary";
                               Salary = 10000;}

Upvotes: 6

Related Questions