F#中的Null Coalescing运算符?

31
与C#库进行交互时,我发现自己希望在Nullable结构和引用类型中都使用C#的null合并运算符。
是否可能使用单个重载运算符在F#中近似实现这一点,并内联适当的if情况?

很好的文章,还包括选项聚合:http://troykershaw.com/blog/null-coalescing-operator-in-fsharp-but-for-options/ - Giles
@Giles 那篇博客文章大部分已经过时了,它仅仅包括选项合并,并且在 F# 中更简洁地表达为 let (|?) = defaultArg - jbtule
我还没有测试过,但是这篇文章确实建议使用空值合并替换形式的 let inline (|??) (a: 'a Nullable) b = if a.HasValue then a.Value else b。我对F#还很陌生,所以我可能错了,但是你的建议(假设有参数)不会导致默认值总是被使用吗? - Giles
不,defaultArg是一个内置函数,它的工作方式与博客文章中描述的 |? 完全相同。你所描述的可空版本具有严重的限制,只能与 Nullable<> 结构一起使用,而不能与任何可能具有 null 值的类型一起使用。但这个问题是针对一个单一运算符的,它可以用于 Options 或 Nullables 或其他变体,而不是为每个略有不同的 monad 添加 |?、|??、|??? 或另一个 ?。只需要一个合并所有的合并运算符。 - jbtule
啊,我明白了(http://msdn.microsoft.com/en-us/library/ee340463.aspx)。感谢您的澄清。 - Giles
3个回答

31

是的,可以使用在这个SO答案"Overload operator in F#"中找到的一些小技巧。

在编译时,可以内联单个运算符的正确重载,例如('a Nullable, 'a) ->'a('a when 'a:null, 'a) -> 'a。甚至可以加入('a option, 'a) -> 'a以获得更大的灵活性。

为了提供更接近C#操作符的行为,我将默认参数'a Lazy设置为只有在原始值为null时才调用其源代码。

示例:

let value = Something.PossiblyNullReturned()
            |?? lazy new SameType()

实现:

NullCoalesce.fs [Gist]:

//https://gist.github.com/jbtule/8477768#file-nullcoalesce-fs
type NullCoalesce =  

    static member Coalesce(a: 'a option, b: 'a Lazy) = 
        match a with 
        | Some a -> a 
        | _ -> b.Value

    static member Coalesce(a: 'a Nullable, b: 'a Lazy) = 
        if a.HasValue then a.Value
        else b.Value

    static member Coalesce(a: 'a when 'a:null, b: 'a Lazy) = 
        match a with 
        | null -> b.Value 
        | _ -> a

let inline nullCoalesceHelper< ^t, ^a, ^b, ^c when (^t or ^a) : (static member Coalesce : ^a * ^b -> ^c)> a b = 
        // calling the statically inferred member
        ((^t or ^a) : (static member Coalesce : ^a * ^b -> ^c) (a, b))

let inline (|??) a b = nullCoalesceHelper<NullCoalesce, _, _, _> a b

另外,我制作了一个库,利用这个技术以及计算表达式来处理Null/Option/Nullables,名为FSharp.Interop.NullOptAble

它使用操作符|?->


1
哇,这是可扩展的:static member Coalesce(a: 'a option ref, b: unit -> 'a) = match a.Value with Some a -> a | _ -> b()。顺便说一下,您可以通过与null匹配来放弃等式约束。 - kaefer
1
如果你愿意,你也可以称之为 |? 运算符。但我真的希望 F# 能够允许我们定义一个 ?? 运算符。;-) - luksan
@luksan 你可以通过F#用户反馈网站https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30935-languages-f-或直接发送电子邮件至fsbugs@microsoft.com提出功能请求。 - Phillip Trelford
我是一个F#新手。它可以与Nullable<int>一起使用吗?当我尝试应用它时,我遇到了编译错误:None of the types 'Nullable<int>, int' support the operator '|??'。让foo = nullableOfInt |?? 6。 - dzendras
只是随口一说,不知道是否可能使用计算表达式来代替懒惰,例如:let foo = nullableOfInt??? <@ 10 @> - Damian

5

修改了被接受的答案,由jbtule支持DBNull:

//https://gist.github.com/tallpeak/7b8beacc8c273acecb5e
open System

let inline isNull value = obj.ReferenceEquals(value, null)
let inline isDBNull value = obj.ReferenceEquals(value, DBNull.Value)

type NullCoalesce =
    static member Coalesce(a: 'a option, b: 'a Lazy) = match a with Some a -> a | _ -> b.Value
    static member Coalesce(a: 'a Nullable, b: 'a Lazy) = if a.HasValue then a.Value else b.Value
    //static member Coalesce(a: 'a when 'a:null, b: 'a Lazy) = match a with null -> b.Value | _ -> a // overridden, so removed
    static member Coalesce(a: DBNull, b: 'b Lazy) = b.Value //added to support DBNull
    // The following line overrides the definition for "'a when 'a:null"
    static member Coalesce(a: obj, b: 'b Lazy) = if isDBNull a || isNull a then b.Value else a // support box DBNull
let inline nullCoalesceHelper< ^t, ^a, ^b, ^c when (^t or ^a) : (static member Coalesce : ^a * ^b -> ^c)> a b = 
                                            ((^t or ^a) : (static member Coalesce : ^a * ^b -> ^c) (a, b))

使用方法:

let inline (|??) a b = nullCoalesceHelper<NullCoalesce, _, _, _> a b
let o = box null
let x = o |?? lazy (box 2)
let y = (DBNull.Value) |?? lazy (box 3)
let z = box (DBNull.Value) |?? lazy (box 4)
let a = None |?? lazy (box 5)
let b = box None |?? lazy (box 6)
let c = (Nullable<int>() ) |?? lazy (7)
let d = box (Nullable<int>() ) |?? lazy (box 8)

我希望这个合并运算符支持DBNull.Value和box(DBNull.Value),这段代码似乎做到了,我认为其他F#用户也可能遇到了同样的要求,并且可能有评论或修改。 - Aaron West

0

我通常使用defaultArg来完成这个任务,因为它是语言内置的。


缺点是它仅适用于选项,并且提供默认值的表达式总是被评估。 - jbtule

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接