定义区分联合的默认值

时间:2018-05-12 09:07:39

标签: f# discriminated-union

我想为受歧视的联合定义一个默认值,如下所示:

let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let result = formatter.string(from: date)
self.backS.sendLocation(msg: "data fetched \(result)")

选项<'一>以这种方式工作(firstResult将是None),所以它应该是可能的。 谢谢你的帮助。

编辑: 我正在使用SQLDataProvider,并希望编写像

这样的代码
"2018-05-12 14:34:51 +0000"

我的实际结果类型如下所示:

open System.Linq

type Result =
| Ok
| Error

let results : seq<Result> = [] |> Seq.ofList

let firstResult = results.FirstOrDefault()
// I want firstResult to be Error, currently it is null.

返回选项后,调用者将无法区分失败和错误。

3 个答案:

答案 0 :(得分:7)

通常,F#避免使用默认值的概念,而是使所有内容尽可能明确。返回一个选项并让调用者决定特定用例的默认值是更惯用的。

FirstOrDefault方法只能为任何类型返回.NET的默认值。因此,对于任何类,它将返回null,对于数字,它将返回零。

我建议您使用此方法,而不是假设您所需的默认值为Ok

results |> Seq.tryHead |> Option.defaultValue Ok

答案 1 :(得分:4)

您可以使用UseNullAsTrueValue编译参数执行此操作。这告诉编译器使用null作为其中一个无参数情况的内部表示。这有点像黑客(因为这主要是为了性能优化,在这里我们有点滥用它,但它可能会有效)。

我没有SQL数据库设置来尝试使用SQL提供程序,但以下工作在内存中,我认为它也适用于SQL:

[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type Result<'a> =
  | Ok of 'a
  | Failure of string
  | Error

let userEmail =
  query {
    for a in [1] do
    where (a = 2)
    select (Ok a)
    headOrDefault }

如果你运行这个,F#interactive会打印出userEmail = null,但那很好,以下是真的:

userEmail = Error

答案 2 :(得分:2)

如果要表示数据或查询参数的问题(例如,未查找特定记录)与其他类型的故障不同,例如未连接到数据库或遇到未处理的异常,则可以创建一个有区别的联合,明确表示您的预期数据/查询问题。然后,您可以返回该DU而不是string作为Error案例的数据,并且您不会真正需要Failure案例。考虑这样的事情:

type SqlError =
| NoMatchingRecordsFound of SqlParameter list
| CouldNotConnectToDatabase
| UserDoesNotHaveQueryPermissions
| UnhandledException of exn

我建议您查看Railway-Oriented Programming并遵循为您的不同错误案例定义歧视联盟的模式。您仍然可以在该DU中包含错误消息,但我建议对所有不同的预期失败使用显式情况,然后使用&#34; UnhandledException&#34;您的意外错误或类似情况,可能是数据exn

如果您有兴趣,我会在GitHub / NuGet上设置一个库,将所有ROP内容放在一起,并增加与TaskAsync的互操作性和一个计算构建器中的Lazy类型,因此您不必将所有代码包含在您自己的项目中。

修改

这是一个完整的ROP风格示例(使用链接框架):

open FSharp.Control
open System.Linq

// Simulate user table from type-provider
[<AllowNullLiteral>]
type User() =
    member val Id = 0 with get,set
    member val Name = "" with get,set

// Simulate a SQL DB
let users = [User(Id = 42, Name = "The User")].AsQueryable()

// Our possible events from the SQL query
type SqlEvent =
| FoundUser
| UserIdDoesNotExist of int
| CouldNotConnectToDatabase
| UnhandledException of exn

// Railway-Oriented function to find the user by id or return the correct error event(s)
let getUserById id =
    operation {
        let user =
            query {
                for user in users do
                where (user.Id = id)
                select user
                headOrDefault
            }

        return!
            if user |> isNull
            then Result.failure [UserIdDoesNotExist id]
            else Result.successWithEvents user [FoundUser]
    }

Caling getUserById 42将返回与用户和事件FoundUser成功完成的操作。为任何其他号码(例如0)调用getUserById将返回错误事件UserIdDoesNotExist 0的失败操作。您可以根据需要添加更多活动。