Question

The following code examples provide context to my question.

The Room class is initialized with a delegate. In the first implementation of the Room class, there are no guards against delegates that throw exceptions. Such exceptions will bubble up to the North property, where the delegate is evaluated (note: the Main() method demonstrates how a Room instance is used in client code):

public sealed class Room
{
    private readonly Func<Room> north;

    public Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = new Room(north: evilDelegate);

        var room = kitchen.North; //<----this will throw

    }
}

Being that I'd rather fail upon object creation rather than when reading the North property, I change the constructor to private, and introduce a static factory method named Create(). This method catches the exception thrown by the delegate, and throws a wrapper exception, having a meaningful exception message:

public sealed class Room
{
    private readonly Func<Room> north;

    private Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static Room Create(Func<Room> north)
    {
        try
        {
            north?.Invoke();
        }
        catch (Exception e)
        {
            throw new Exception(
              message: "Initialized with an evil delegate!", innerException: e);
        }

        return new Room(north);
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = Room.Create(north: evilDelegate); //<----this will throw

        var room = kitchen.North;
    }
}

Does the try-catch block render the Create() method impure?

Was it helpful?

Solution

Yes. That is effectively an impure function. It creates a side-effect: program execution continues somewhere other than the place to which the function is expected to return.

To make it a pure function, return an actual object that encapsulates the expected value from the function and a value indicating a possible error condition, like a Maybe object or a Unit of Work object.

OTHER TIPS

Well, yes... and no.

A pure function must have Referential Transparency - that is, you should be able to replace any possible call to a pure function with the returned value, without changing the program's behavior.* Your function is guaranteed to always throw for certain arguments, so there is no return value to replace the function call with, so instead let's ignore it. Consider this code:

{
    var ignoreThis = func(arg);
}

If func is pure, an optimizer could decide that func(arg) could be replaced with it's result. It does not know yet what the result is, but it can tell that it's not being used - so it can just deduce this statement has no effect and remove it.

But if func(arg) happens to throw, this statement does do something - it throws an exception! So the optimizer can not remove it - it does matter if the function get called or not.

But...

In practice, this matters very little. An exception - at least in C# - is something exceptional. You are not supposed to use it as part of your regular control flow - you are supposed to try and catch it, and if you do catch something handle the error to either revert what you were doing or to somehow still accomplish it. If your program does not work properly because a code that would have failed was optimized away, you are using exceptions wrong(unless it's test code, and when you build for tests exceptions should not be optimized).

That being said...

Don't throw exceptions from pure functions with the intention that they'll be catched - there is a good reason functional languages prefer to use monads instead of stack-unwinding-exceptions.

If C# had an Error class like Java(and many other languages), I would have suggested to throw an Error instead of an Exception. It indicates that the user of the function did something wrong(passed a function that throws), and such things are allowed in pure functions. But C# does not have an Error class, and the usage error exceptions seem to derive from Exception. My suggestion is to throw an ArgumentException, making it clear that the function was called with a bad argument.


* Computationally speaking. A Fibonacci function implemented using naive recursion will take a long time for large numbers, and may exhaust the machine's resources, but these since with limitless time and memory the function will always return the same value and will not have side-effects(other than allocating memory and altering that memory) - it's still considered pure.

One consideration is that the try - catch block is not the issue. (Based on my comments to the question above).

The main problem is that the North property is an I/O call.

At that point in the code's execution, the program needs to check the I/O provided by the client code. (It would not be relevant that the input is in the form of a delegate, or that the input was, nominally, passed in already).

Once you lose control of the input, you cannot ensure the function is pure. (Especially if the function can throw).


I'm not clear why you do not want to check on the call to Move[Check]Room? As per my comment to the question:

Yes. What do you when executing the delegate may only work the first time, or return a different room on the second call? Calling the delegate can be considered / cause a side effect itself?

As Bart van Ingen Schenau said above,

Your Create function does not protect you from getting an exception when getting the property. If your delegate throws, in real life it is very likely that it will thrown only under some conditions. Chances are that the conditions for throwing are not present during construction, but they are present when getting the property.

In general, any type of lazy loading implicitly defers the errors until that point.


I would suggest using a Move[Check]Room method. This would allow you to separate the impure I/O aspects into one place.

Similar to Robert Harvey's answer:

To make it a pure function, return an actual object that encapsulates the expected value from the function and a value indicating a possible error condition, like a Maybe object or a Unit of Work object.

It would be up to the code writer to determine how to handle the (possible) exception from the input. Then the method can return a Room object, or a Null Room object, or perhaps bubble out the exception.

It this point it depends on:

  • Does the Room domain treat Room Exceptions as Null or Something Worse.
  • How to notify the client code calling North on a Null / Exception Room. (Bail / Status Variable / Global State / Return a Monad / Whatever; Some are more pure then others :) ).

Hmm... I didn't feel right about this. I think what you arw trying to do is to make sure other people do their job right, without knowing what they are doing.

Since the function is passed in by the consumer, which could be written by you or by other person.

What if the function pass in is not valid to run at the time the Room object is created?

From the code, I couldn't be sure what the North is doing.

Let say, if the function is to book the room, and it need the time and the period of booking, then you would get exception if you have not have the information ready.

What if it is a long running function? You will not want to block your program while creating a Room object, what if you need to create 1000 Room object?

If you were the person who will write the consumer that consume this class, you would make sure the function passed in is written correctly, wouldn't you?

If there is another person who write the consumer, he/she might not know the "special" condition of create a Room object, which could cause execption and they would scratch their head trying to find out why.

Another thing is, it not always a good thing to throw a new exception. Reason being is it will not contain the information of the original exception. You know you get an error (a friendly mesaage for generic exception), but you wouldn't know what is the error (null reference, index out of bound, devide by zero etc). This information is very important when we need to do investigation.

You should handle the exception only when you know what and how to handle it.

I think your original code is good enough, the only thing is you might want to handle the exception on the "Main" (or any layer that would know how to handle it).

I will disagree with the other answers and say No. Exceptions does not cause an otherwise pure function to become impure.

Any use of exceptions could be rewritten to use explicit checks for error results. Exceptions could just be considered syntactic sugar on top of this. Convenient syntactic sugar does not make pure code impure.

Licensed under: CC-BY-SA with attribution
scroll top