【问题标题】:implementing an equivalent of the C#'s null test in F#在 F# 中实现相当于 C# 的 null 测试
【发布时间】:2021-02-14 12:14:47
【问题描述】:

这段代码我用了很多:

let inline (||>) (a: 'a option) (b: 'a -> unit) = if a.IsSome then b a.Value

所以我可以做类似的事情

myData ||> DoSomethingWithIt

不必测试 myData 是 Some 还是 None,因为有许多函数通常不需要测试选项。这样可以避免将测试放在函数本身中。

我想将此扩展到可以像 C# 那样做的类型的方法:

myData?.DoSomethingWithIt

基本上替换:

if myData.IsSome then myData.Value.DoSomethingWithIt

加上一些语法糖。

但我不知道如何操作运算符,以便它允许访问表达式中的类型方法。这在 F# 中可能吗?

我也愿意了解为什么如果它是一个坏主意:)

【问题讨论】:

  • 重写||> 是个坏主意,因为它是用于元组解构(1, 2) ||> printfn "%d %d" 的预定义运算符,如果有人试图打开您的模块,某些代码可能会中断
  • @JL0PD,我不知道!谢谢你告诉我

标签: f#


【解决方案1】:

根据 DoSomethingWithIt 的返回类型,F# 库提供了一些标准函数,用于处理可转换为运算符的选项。

let x = Some 1

let aPrinter a = printfn "%i" a
let add1 a = a + 1

let (|?>) opt f = Option.iter f opt
let (|??>) opt f = Option.map f opt

x |?> aPrinter
let y = x |??> add1

您还可以考虑重新定义您的 DoSomethingWithIt 以使用部分应用选项。

let DoSomethingWithIt' = Option.iter DoSomethingWithIt 
let something' = Option.iter (fun (b:B) -> b.DoSomethingWithIt()) //For instance methods

这最终可能需要大量工作,具体取决于您要处理的函数数量。 最终,您不应该试图隐藏您正在使用 Options 的事实。通过将某些东西设置为选项,您是在告诉编译器您不确定它是否存在。它试图通过强迫您处理 None 案例来帮助您。如果您的代码中有很多情况下您知道您的 Option 是 Some 那么您的代码中可能存在更大的体系结构问题,您应该在使用它们之前尝试将所有 Option 提升到仅 T 。例如:

let lift xs = 
    [
        for x in xs do 
            match x with
            | Some x -> yield x
            | None -> ()
    ]

【讨论】:

  • 为了提供一些上下文,我正在处理服务器发送的大量消息,这些消息可能/可能不包含某些值,具体取决于更新的数据。因此,虽然我知道类型是可选的,但几乎每一次交互都只有在类型为 Some 时才会发生。但由于这种情况下很容易有超过 50 个字段,我试图隐藏检查。但我不知道 Option.iter 正是我需要的!
  • 在这种情况下,我建议最强大的解决方案是编写一个 messageClassifier 函数,将每条消息转换为几种类型之一(包装在 DU 中),基于字段实际上有值。这样您就可以预先消除所有选项,并明确您正在处理的每种消息。
  • 但是组合的数量会激增。这是我收到的消息 (pastebin.com/iCa5eEdu) 的示例,它源自 C#。有 29 个字段,在任何给定时间,大约一半可能不存在于 json 消息中,可能有 10 个左右是强制性的。我无法列出所有组合;还是我错过了什么?
  • 原则上可能有数十亿个组合,但实际上服务器实际发送的组合可能只有少数几个。嵌套结构(另一个 DU 的 DU)也可能是合适的。
  • 种类非常多,因为数据跟踪金融交易,字段可能存在或不存在,基于自上次以来发生变化的计算,所以服务器有点期待客户端根据缺少的字段制作增量。而且,当然,我只想处理更新的,所以我不能只创建一个增量并运行运行处理。我可以做一些但非常有限的字段分组,但总的来说,它们大多必须一个一个地处理
【解决方案2】:

看看 Option.iter。它与您的操作员具有相同的签名。

【讨论】:

    【解决方案3】:

    这种结构没有类似的语法,但 F# 有替代方案。 最简单的方法是使用FSharpx.Extras 库和FSharpx.Option.maybe computation expression 这将允许您使用Option 相关操作。

    open FSharpx.Option
    
    let a = Some 1
    let b = maybe {
        let! v = a
        return v + 3
    } // b is (Some 4)
    
    let c : int option = None
    let d = maybe {
        let! v = c
        return v + 3 // this line won't be reached
    } // d is None
    

    【讨论】:

    • 那行不通,因为我们的想法是让它尽可能简洁,如果我要使用计算表达式,还不如对值进行测试,然后运行代码如果测试通过
    【解决方案4】:

    我相信 c# 中的 ?. 运算符是一种语法糖,它隐藏了在调用类型成员之前检查 null 的 if 语句。即使你可以让它按照你的计划工作,我觉得它会违反 FP 原则,并且可能会导致更多的问题。

    Option 模块可能包含您已经需要的大部分内容。 iter 函数允许对 Option 的值调用函数if 该值存在 (Some)。

    如果您的输入参数可以是空值,而不是选项,您可以使用 Option.ofObj 函数,如果参数不为空,该函数会将参数转换为带有 Some 的选项, 否则

    所以假设你的函数DoSomethingWithit 接受一个字符串并返回单位:

       let DoSomethingWithIt = //(string -> unit)
           printf "%s; "
    

    您可以使用这种更详细的语法来(例如)迭代列表中的可为空值:

       let lst = [ "data"; "data 2"; null; "data3" ]
       lst
       |> List.iter (fun v -> v |> Option.ofObj |> Option.iter DoSomethingWithIt)
    

    或者,您可以编写 Optioni.ofObjOption.iter DoSomethingWithIt 函数并执行类似的操作

       let SafeDoSomethingWithIt = //(string -> unit)
          Option.ofObj >> Option.iter DoSomethingWithIt 
    

    这为您提供了安全的调用:

       let lst2 = [ "data"; "data 2"; null; "data3" ]
       lst2
       |> List.iter SafeDoSomethingWithIt
    

    您可以概括返回 unit 的函数组合(但不仅如此)

       let makeSafe fn =
          Option.ofObj >> Option.iter fn
    

    然后可以创建一系列安全函数:

       let SafeDoSomethingWithIt = makeSafe DoSomethingWithIt 
       
       let safePrint = makeSafe (printf "%s; ")
    
       //...etc
    

    那么这仍然有效:

       lst2
       |> List.iter SafeDoSomethingWithIt
       
       lst2
       |> List.iter safePrint
    

    您还可以使用 Option.bind 函数为返回值的函数编写包装器。

       let makeSafeReturn fn = //(string -> string option)
           Option.ofObj >> Option.bind fn
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2012-05-31
      • 1970-01-01
      • 2019-08-11
      相关资源
      最近更新 更多