【问题标题】:Is this a candidate for computational expressions?这是计算表达式的候选者吗?
【发布时间】:2011-05-09 15:34:48
【问题描述】:

我有以下 c# 代码,它会检查权限。我想知道,当转换为 f# 时,计算表达式是否会成为一种排除空值检查的方法。

bool ShouldGrantPermission(ISecurityService security, User user, Item item) {
  return user != null && item != null && user.Id == item.AuthorId
    && security.Does(user).Have(MyPermission).On(item);
}

我想指出,如果任何项目为空,ISecurityService API 当前会返回 false。但是它会进行数据库调用,因此这里的代码会检查 null 然后进行 id 检查,因为在大多数情况下,这将返回 false 并避免数据库调用。

【问题讨论】:

    标签: c# f# monads


    【解决方案1】:

    您可以定义一个隐藏null 检查的计算构建器,但它并没有给您提供非常方便的语法,所以我可能不会那样写。如果有一些更轻量级的语法会很酷,因为它会非常有用。此外,计算构建器只传播null,因此您将以Nullable<bool> 类型的结果结束:

    nullable { let! u = user
               let! i = item
               return u.Id == i.AuthorId && security.Does(user).Have(MyPermission).On(i) }
    

    这个想法是let! 操作仅在参数不是null 时调用其余的计算。当为null时,立即返回null作为整体结果。

    我不认为你可以做很多事情来使代码更好。当然,如果全部用 F# 编写,则没有一个值可以是 null(因为 F# 声明的类型不允许 null 值),但这是另一回事。

    F# 中的另一种方法是声明一个活动模式,仅当值不是null 时才匹配。这样做的好处是您不会在代码中包含任何可能具有 null 值的变量,因此不存在使用错误变量并获得 NullReferenceException 的危险:

    let shouldGrantPermission = function
      | NotNull(security:ISecurityService), NotNull(user), NotNull(item) ->
          security.Does(user).Have(MyPermission).On(item)
      | _ -> true
    

    活动模式的声明是:

    let (|NotNull|_|) a = if a <> null then Some(a) else None
    

    然而,即使这也并不比你所拥有的直接等价物好太多。我想处理null 值只是痛苦:-)。这个article by Ian Griffiths有一些相关的想法,但同样,它们都没有真正解决问题。

    【讨论】:

    • 感谢您的回答,我现在对我对 monads 的理解不那么担心了。 :)
    【解决方案2】:

    我会对 Tomas 的回答稍作调整:使用 Object.ReferenceEquals 代替 = 进行空值检查。它更快,更重要的是,您不必使用 AllowNullLiteral 属性标记在 F# 中声明的类型。我通常为将在 C# 中使用的 F# 代码定义一个 Interop 模块。这隔离了 null 处理,并且由于它不需要使用 [&lt;AllowNullLiteral&gt;],因此您可以在 F# 中忽略 null 并仅在与 C# 交互时处理它(即,您的公共接口)。这是我使用的模块(复制自this answer):

    [<AutoOpen>]
    module Interop =
    
        let inline (===) a b = obj.ReferenceEquals(a, b)
        let inline (<=>) a b = not (a === b)
        let inline isNull value = value === null
        let inline nil<'T> = Unchecked.defaultof<'T>
        let inline safeUnbox value = if isNull value then nil else unbox value
        let (|Null|_|) value = if isNull value then Some() else None
    
    type Foo() = class end
    
    type Test() =
        member this.AcceptFoo(foo:Foo) = //passed from C#
            if isNull foo then nullArg "foo"
            else ...
    
        member this.AcceptFoo2(foo:Foo) = //passed from C#
            match foo with
            | Null -> nullArg "foo"
            | _ -> ...
    
        member this.AcceptBoxedFoo(boxedFoo:obj) =
            let foo : Foo = safeUnbox boxedFoo
            ...
    
        member this.ReturnFoo() : Foo = //returning to C#
            if (test) then new Foo()
            else nil
    

    Snippet 在 fssnip.net 上。

    【讨论】:

    • !== 的唯一问题是 ! 在 F# 中的其他任何地方并不意味着“不”。有些人可能会称其为可憎,但我几乎认为&lt;&lt;&gt;&gt; 将是=== 更惯用的伴侣。
    • @Stephen - 我对您的运营商的外观并不十分感兴趣,但我不能指责您的推理。
    • 很遗憾 F# 不支持 unicode 运算符!例如,我们可以使用=•=&lt;•&gt;(子弹代表null,这非常合适,因为它非常适合射中自己的脚:-))。
    • @Tomas:“很遗憾 F# 不支持 unicode 运算符!”。阿门!我希望能够使用 ×、±、⊕、⊗ 等。还有标识符中的∂...
    • @Jon - 然后你也可以用你的图书馆卖一个特殊的键盘:-)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多