【问题标题】:Return a modified version of same type in F#在 F# 中返回相同类型的修改版本
【发布时间】:2015-06-21 23:57:19
【问题描述】:

如果我有像这样的类层次结构

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

我想要一个以纯方式更新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

我想出的最接近的方法是创建一个虚拟 Employee.changeName 并在每个类上实现它。这似乎需要做很多额外的工作加上它很容易出错,因为返回类型是 Employee 并且必须向上转换回原始类。

似乎应该有一种更简单、更安全的方法来完成这样的任务。这是需要类型类的地方吗?

更新

是的,我可以让 name 字段可变,这就是它现在在我的代码中实现的方式,但这正是我想要摆脱的。

更新 2

我提出的满足类型安全性和简洁性要求的解决方案是定义

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

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

【问题讨论】:

  • 如果您打算让您的财产可写,为什么不“简单地”制作 property writable 呢? member val name = name with get, set
  • @Sehnsucht 我应该提到,这就是我现在正在做的事情,但想要更纯粹地实现事物。

标签: f#


【解决方案1】:

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

在 F# 中,您可以像这样使用代数数据类型为 Employee 建模:

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

type Employee =
| Hourly of HourlyData
| Salaried of SalaryData

这将使您能够像这样创建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;}

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

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

这使您可以像这样更改现有 Employee 值的名称:

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

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

【讨论】:

  • 还是比我想的要冗长一些;对于大量案例,您必须为每个案例重复 | Blah b -&gt; Blah { b with Name = newName。我想我会使用这个想法,但将其限制在Employee 记录的子字段中。所以,type Employee = {name: string, payData: PayData},其中PayData 是一个 ADT。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
  • 2016-03-22
  • 2018-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多