Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a candidate for computational expressions?

Tags:

c#

monads

f#

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 out 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.

like image 371
Charles Lambert Avatar asked May 09 '11 15:05

Charles Lambert


2 Answers

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.

like image 187
Tomas Petricek Avatar answered Sep 23 '22 15:09

Tomas Petricek


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.

like image 41
Daniel Avatar answered Sep 22 '22 15:09

Daniel