在F#中返回相同类型的修改版本

lob*_*ism 3 f#

如果我有一个类层次结构,如

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
Run Code Online (Sandbox Code Playgroud)

我想要一个name以纯粹方式更新字段的函数,这怎么可能?一对失败的选择:

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
Run Code Online (Sandbox Code Playgroud)

我提出的最接近的事情就是虚拟Employee.changeName并在每个类上实现它.这似乎是很多额外的工作加上它容易出错,因为返回类型是Employee并且必须被上传回原始类.

似乎应该有一种更简单,更安全的方式来完成这样的任务.这是必须使用类型类的东西吗?

更新

是的,我可以让name字段变得可变,这就是我现在在代码中实现的方式,但这就是我想要摆脱的东西.

更新2

我想出的解决类型安全性和简明性要求的解决方案就是定义

type Employee<'a> = {name: string; otherStuff: 'a}
Run Code Online (Sandbox Code Playgroud)

然后只需使用with语法来更改名称.但otherStuff: 'a显然是丑陋而且看起来很丑陋的代码,所以我仍然愿意接受更好的解决方案.

Mar*_*ann 6

如果您正在寻找纯粹的和惯用的F#,那么您不应该首先使用继承层次结构.这是一个面向对象的概念.

在F#中,您可以使用代数数据类型对Employee进行建模:

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

type Employee =
| Hourly of HourlyData
| Salaried of SalaryData
Run Code Online (Sandbox Code Playgroud)

这将使您能够创建如下Employee值:

> 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;}
Run Code Online (Sandbox Code Playgroud)

您还可以定义一个以纯方式更改名称的函数:

let changeName newName = function
    | Hourly h -> Hourly { h with Name = newName }
    | Salaried s -> Salaried { s with Name = newName }
Run Code Online (Sandbox Code Playgroud)

这使您可以更改现有Employee值的名称,如下所示:

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

val se' : Employee = Salaried {Name = "Mary";
                               Salary = 10000;}
Run Code Online (Sandbox Code Playgroud)