Is this a candidate for computational expressions?
I have the following c# code, it does a check on permissions. I'm wondering if, when converted to f#, would computational expressions be a way to factor ou开发者_高级运维t the null checks.
bool ShouldGrantPermission(ISecurityService security, User user, Item item) {
return user != null && item != null && user.Id == item.AuthorId
&& security.Does(user).Have(MyPermission).On(item);
}
I would like to note that the ISecurityService API currently returns false if any of the items are null. However it makes a database call, so the code here checks for null and then does the id check, because in most cases this will return false and avoid a database call.
You can define a computation builder that hides null
checking, but it doesn't give you a very convenient syntax, so I probably wouldn't write it that way. It would be cool if there was some more lightweight syntax for this, because it would be quite useful. Also, the computation builder just propagates the null
, so you would end with a result of type Nullable<bool>
:
nullable { let! u = user
let! i = item
return u.Id == i.AuthorId && security.Does(user).Have(MyPermission).On(i) }
The idea is that the let!
operation calls the rest of the computation only when the argument is not null
. When it is null
, it immediately returns null
as the overall result.
I don't think there is much you could do to make the code nicer. Of course, if it was all written in F#, then none of the values could be null
(because F# declared types do not permit the null
value), but that's a different story.
Another approach in F# would be to declare an active pattern that matches only when a value is not null
. This has the benefit that you won't have any variables that may have null
value in the code, so there is no danger of using a wrong variable and getting NullReferenceException
:
let shouldGrantPermission = function
| NotNull(security:ISecurityService), NotNull(user), NotNull(item) ->
security.Does(user).Have(MyPermission).On(item)
| _ -> true
The declaration of the active pattern is:
let (|NotNull|_|) a = if a <> null then Some(a) else None
However, even this isn't really too much nicer than the direct equivalent of what you have. I guess dealing with null
values is just pain :-). This article by Ian Griffiths has some related ideas, but again, none of them really solves the problem.
I would make one slight adjustment to Tomas's answer: use Object.ReferenceEquals
to do the null check instead of =
. It's faster and, more importantly, you don't have to mark types declared in F# with the AllowNullLiteral
attribute. I generally define an Interop
module for F# code that will be used from C#. This isolates null handling, and since it doesn't require the use of [<AllowNullLiteral>]
, you can ignore null within F# and only deal with it at the point of interaction with C# (i.e., your public interface). Here's the module I use (copied from 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 on fssnip.net.
精彩评论