F# 是否可以识别 DU 的重叠并使用正确的重叠本身?
type GenericResult =
| Ok
| Error of string
type LoginResult =
| Ok
| UserNotFound
| WrongPassword
let check something:GenericResult =
match something with
//| true -> Ok // error:This expression was expected to be of type "GenericREsult" but here has type "LoginResult"
| true -> GenericResult.Ok // I'm forced to specify GenericResult.Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok // here I don't need to specify the DU because this is defined after
| _ -> WrongPassword
我想在这两种方法中只使用“Ok”,而不需要指定 DU。
我发现,如果值发生冲突,最后一个是“预定义的”。
理想情况下,我希望拥有某种继承权
在另一个 DU 中重用一个 DU 的一部分。
例如:
type GenericResult =
| Ok
| Error of string
type LoginResult =
//| GenericResult.Ok
| UserNotFound
| WrongPassword
type SaveResult =
| Created
| Updated
//| GenericResult.Error
let checkLogin something: LoginResult | GenericResult.Ok =
match something with
| true -> Ok
| _ -> WrongPassword
[编辑] 我觉得需要此功能的真实场景是来自 3 个不同逻辑类的 3 个不同结果。
将来会出现更多情况,因此重复 DU 值的倍数将会增加。
// DUs ordered from the most specific to the most generic
type BalanceUpdateResult =
| Created
| Updated
| InvalidRequest of string
type DeleteResult =
| Ok
| InvalidRequest of string
type Result<'T> =
| Ok of 'T
| NotValid of string
| Error of string
目标是在使用者中拥有干净的匹配语法,例如,DU 的值最终将用于引发异常或返回创建的值。
// balance update function (result is BalanceUpdateResult):
match result with
| Created -> this.createOkWithStatus 201
| Updated -> this.createOkWithStatus 200
| InvalidRequest error -> this.createErrorForConflict error
// company creation function (result is Result<Company>):
match result with
| Result.Ok newItem ->
context.Logger.Log $"Company created. New Id:{newItem.Id}, Name:{newItem.Name}."
this.createCreated newItem
| NotValid error -> base.createErrorForConflict error
| Error error -> base.createError error
例如,在第二种情况下,InvalidRequest 不被接受,因为它属于错误的 DU。
必须在各处指定 DU 会导致混乱,如下例所示(请参阅许多结果<_>。):
interface ICompanyLogic with
member this.Create(company:Company):Result<Company> =
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid validCompany ->
match companyRepository.Exists(validCompany.Name) with
| true -> Result<_>.NotValid($"A company with name \"{validCompany.Name}\" already exists.")
| _ ->
let newCompany = assignNewId validCompany
companyRepository.Create(newCompany)
Result<_>.Ok(newCompany)
member this.Update (company:Company):Result<Company> =
let checkNameExists company =
match companyRepository.GetByName company.Name with
| Some c when c.Id <> company.Id -> NotValid $"A company with name \"{company.Name}\" already exists."
| _ -> Valid company
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid c -> match checkNameExists c with
| Valid c -> companyRepository.Update c; Result<_>.Ok c
| NotValid msg -> Result<_>.NotValid msg
type GenericResult =
| Ok
| Error of string
type LoginResult =
| Ok
| UserNotFound
| WrongPassword
let check something:GenericResult =
match something with
//| true -> Ok // error:This expression was expected to be of type "GenericREsult" but here has type "LoginResult"
| true -> GenericResult.Ok // I'm forced to specify GenericResult.Ok
| false -> Error "aargg!"
let checkLogin something:LoginResult =
match something with
| true -> Ok // here I don't need to specify the DU because this is defined after
| _ -> WrongPassword
I'd like to use just "Ok" in both the methods, without the need to specify the DU.
I see that in case of clashing of the value the last one is the "predefined".
Ideally I'd like to have a sort of inheritance
to reuse part of a DU in another DU.
For example:
type GenericResult =
| Ok
| Error of string
type LoginResult =
//| GenericResult.Ok
| UserNotFound
| WrongPassword
type SaveResult =
| Created
| Updated
//| GenericResult.Error
let checkLogin something: LoginResult | GenericResult.Ok =
match something with
| true -> Ok
| _ -> WrongPassword
[EDIT]
The real scenario where I feel the need for this feature is this with 3 different results from 3 different logic classes.
There will be in the future more cases so the multiplication of duplicated DU values will increase.
// DUs ordered from the most specific to the most generic
type BalanceUpdateResult =
| Created
| Updated
| InvalidRequest of string
type DeleteResult =
| Ok
| InvalidRequest of string
type Result<'T> =
| Ok of 'T
| NotValid of string
| Error of string
The goal is to have a clean match syntax in the consumer, where the value of the DU will evenctually be used to raise an exception or to return the created value, for example.
// balance update function (result is BalanceUpdateResult):
match result with
| Created -> this.createOkWithStatus 201
| Updated -> this.createOkWithStatus 200
| InvalidRequest error -> this.createErrorForConflict error
// company creation function (result is Result<Company>):
match result with
| Result.Ok newItem ->
context.Logger.Log quot;Company created. New Id:{newItem.Id}, Name:{newItem.Name}."
this.createCreated newItem
| NotValid error -> base.createErrorForConflict error
| Error error -> base.createError error
Here, for example, InvalidRequest is not accepted in the second case because it belongs to the wrong DU.
Having to specify the DU everywhere results in a mess like the following example (see the many Result<_>.):
interface ICompanyLogic with
member this.Create(company:Company):Result<Company> =
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid validCompany ->
match companyRepository.Exists(validCompany.Name) with
| true -> Result<_>.NotValid(quot;A company with name \"{validCompany.Name}\" already exists.")
| _ ->
let newCompany = assignNewId validCompany
companyRepository.Create(newCompany)
Result<_>.Ok(newCompany)
member this.Update (company:Company):Result<Company> =
let checkNameExists company =
match companyRepository.GetByName company.Name with
| Some c when c.Id <> company.Id -> NotValid quot;A company with name \"{company.Name}\" already exists."
| _ -> Valid company
match normalize company |> validate with
| NotValid msg -> Result<_>.NotValid msg
| Valid c -> match checkNameExists c with
| Valid c -> companyRepository.Update c; Result<_>.Ok c
| NotValid msg -> Result<_>.NotValid msg
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为实现您想要做的事情的最佳方法是从通用
Result
类型开始,该类型具有表示错误类型的类型参数:这允许您使用不同的类型来表示错误,包括
string
,还有另一个 DU 来捕获更具体的错误类型。然后,您可以将GenericResult
和LoginResult
定义为两个类型别名:要报告登录错误,您现在可以使用
Error WrongPassword
来包装特定错误在通用Error
构造函数中。这两个函数的实现如下所示:I think the best way to achieve what you are trying to do would be to start with a generic
Result
type that has a type parameter representing the error type:This allows you to use different types for representing errors, including
string
, but also another DU to capture more specific error types. You can then defineGenericResult
andLoginResult
as two type aliases:To report a login error, you would now use
Error WrongPassword
to wrap the specific error in the genericError
constructor. The implementation of your two functions looks as follows:与 TypeScript 联合类型不同,F# DU 旨在组合且不可扩展 - 请参阅 Thomas 答案 了解使用此方法的解决方案。
由于 F# 不提供直接的解决方案,您可以考虑重命名诸如
InvalidRequest
之类的情况,以便更具体并帮助在阅读代码时区分它们。使用这些特定名称,您还可以将所有结果类型合并到一个大的Event
DU 中,就像在事件源系统中通常所做的那样:Unlike TypeScript union type, F# DU are meant to be composed and not extensible - see Thomas answer for a solution using this approach.
Since F# does not offer a direct solution, you may consider renaming cases like
InvalidRequest
in order to be more specific and to help differentiate them when reading the code. With these specific names, you can also merge all result types into a bigEvent
DU like what's usually done in an event sourced system:好的,正如 Romain 所解释的,多个 DU 无法解决我的问题。
我决定使用内置的
type Result< 'T,'TError>
。它使我能够避免创建许多不可避免地会发生名称冲突的 DU,从而强制在代码中使用完整的 DU 前缀。
我通过 Thomas 回复中鼓舞人心的示例解决了促使我创建自定义 DU 的问题。
(结果<,>)我有可能出现明显的错误或正常。
(注意
Result
和Result
)除了
BalanceUpdateResult
之外,所有其他替换的 DU 都购买 <代码>结果<'T,'TError>。我只是为特定任务维护了几个:
最后使用此解决方案:
Ok, as explained by Romain multiple DUs cannot solve my problem.
I decided to use the built-in
type Result<'T,'TError>
.It allows me to avoid create many DUs that inevitably will have clash of names, forcing the use the full DU prefix in the code.
I solved the problem that drove me to create custom DUs with the inspiring example from Thomas reply.
(with Result<,>) I have the possibility to have dinstinct Errors or Oks.
(note the
Result<unit,_>
and theResult<BalanceUpdateRequest,_>
)Apart
BalanceUpdateResult
all the other DUs where replaced buy theResult<'T,'TError>
.I just maintained a couple one for specific tasks:
In the end with this solution: