28

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?

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 1
    What is the benefit of the Room Create function; if your using a delegate why call it before the client calls 'North'? – SH- Oct 26 '16 at 22:07
  • 1
    @SH If the delegate throws an exception, I want to find out upon creation of the Room object. I don't want the client to find the exception upon usage, but rather upon creation. The Create method is the perfect place to expose evil delegates. – Rock Anthony Johnson Oct 26 '16 at 22:11
  • 3
    @RockAnthonyJohnson, 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? – SH- Oct 26 '16 at 22:17
  • @SH You bring up a good point. If the delegate that is passed into Create is impure, does that make Create impure? Any have an answer for this? – Rock Anthony Johnson Oct 26 '16 at 22:21
  • 4
    A function that calls an impure function is an impure function, so if the delegate is impure `Create` is also impure, because it calls it. – Idan Arye Oct 27 '16 at 00:19
  • 2
    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. – Bart van Ingen Schenau Oct 27 '16 at 18:22
  • Very true, @BartvanIngenSchenau. A very careless, or malicious, programmer could put some funky stuff within the delegate. The Create method provides only a minimal level of protection. If only C# had a 'lazy' keyword for method arguments, all of this could be avoided (I think). – Rock Anthony Johnson Oct 27 '16 at 18:27

5 Answers5

26

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.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • Wow!! So, pure functions should NEVER throw? Me being an OO guy, this is rocking my world upside-down! Haha! – Rock Anthony Johnson Oct 26 '16 at 15:58
  • 16
    Remember, "pure" is just a definition. If you need a function that throws, but in every other way it is referentially transparent, then that's what you write. – Robert Harvey Oct 26 '16 at 16:00
  • 1
    In haskell at least any function can throw but you have to be in IO to catch, a pure function that sometimes throws would normally be called partial – jk. Oct 26 '16 at 16:38
  • 4
    I've had an epiphany because of your comment. While I'm intellectually intrigued by purity, what I actually need in practicality is determinism and reverential transparency. If I achieve purity, that's just an added bonus. Thank you. FYI, the thing that's driven me down this path is the fact that Microsoft IntelliTest is effective only against code that is deterministic. – Rock Anthony Johnson Oct 26 '16 at 16:38
  • 4
    @RockAnthonyJohnson: Well, *all code should be deterministic,* or at least as deterministic as possible. Referential transparency is just one way to get that determinism. Some techniques, like threads, have a tendency to work against that determinism, so there are other techniques like Tasks that improve the threading model's non-deterministic tendencies. Things like random number generators and Monte-Carlo simulators are also deterministic, but in a different way based on probabilities. – Robert Harvey Oct 26 '16 at 16:40
  • My understanding is that throwing doesn't cause a side effect. It's just a fancy form of returning in the concept of a function. C# has a lot of syntactic sugar around handling these fancy return values, but in the end `throw` is just sugar around `return`. I'd argue that throwing an exception is pure because you can show that the exact same input produces the exact same output and does not modify static or global state. – zzzzBov Oct 26 '16 at 16:53
  • 1
    @zzzzBov: Throwing doesn't produce an output at all, at least not in the sense that you would expect from a pure function. – Robert Harvey Oct 26 '16 at 16:55
  • @RobertHarvey, it absolutely does. The output it returns is an exception object, which when thrown is handled in a special manner (hence why i called it syntactic sugar). The handling of the return value is essentially done outside of the called function with `try..catch` being its own special form of `goto`. – zzzzBov Oct 26 '16 at 16:57
  • 1
    @zzzzBov: It won't return to the expected place with that object. It will return somewhere else. You can't treat that in a referentially transparent way; if you compose a larger function using a function that throws, there's no guarantee you will get any value at all back from your function stack. If it throws, you won't. – Robert Harvey Oct 26 '16 at 17:00
  • @RobertHarvey, all functions in C# are able to return in multiple locations. One is from the `return` statement, and another is from `catch`. As I said, I view it as useful syntactic sugar around the concept of a `return`, but this is getting to where I need you to cite *your* favorite source for the definition of a "pure function" and I'll need to cite *my* favorite source for the definition of "pure function" and then we each disagree that the others definitions are either too restrictive or permissive. – zzzzBov Oct 26 '16 at 17:03
  • Good answer, but I wouldn't call it the `Maybe Monad`, just the `Maybe Type`. It may be a monad (in Haskell), but that isn't really relevant to its use in this situation. – gardenhead Oct 26 '16 at 19:19
  • 3
    @zzzzBov: I don't consider the actual, strict definition of "pure" all that important. See [the second comment, above.](http://softwareengineering.stackexchange.com/questions/334675/does-catching-throwing-exceptions-render-an-otherwise-pure-method-to-be-impure/334676?noredirect=1#comment714414_334676) – Robert Harvey Oct 26 '16 at 19:20
  • I've tried and like @RobertHarvey 's suggestion of a return object. Vladimir Khorikov has a nice implementation of such and blogs about it at http://enterprisecraftsmanship.com/2015/03/20/functional-c-handling-failures-input-errors/ – Rock Anthony Johnson Oct 27 '16 at 01:16
13

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.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Idan Arye
  • 12,032
  • 31
  • 40
  • 1
    +1 For your suggested use of ArgumentException, and for your recommendation to not "throw exceptions from pure functions with the intention that they'll be catched". – Rock Anthony Johnson Oct 26 '16 at 18:28
  • 1
    Your first argument (until the "But...") seems unsound to me. The exception is part of the contract of the function. You can, conceptionally, rewrite any function as `(value, exception) = func(arg)` and remove the possibility of throwing an exception (in some languages and for some types of exceptions you actually need to list it with the definition, same as arguments or return value). Now it would be just as pure as before (provided it *always* returns a.k.a throws an exception given the same argument). Throwing an exception is *not* a side effect, if you use this interpretation. – AnoE Oct 26 '16 at 22:56
  • @AnoE If you would have written `func` that way, the block I have given could have been optimized away, because instead of unwinding the stack the thrown exception would have been encoded in `ignoreThis`, which we ignore. Unlike exceptions that unwind the stack, exception encoded in the return type do not violate purity - and that's why many functional languages prefer to use them. – Idan Arye Oct 27 '16 at 00:13
  • Exactly... you would not have ignored the exception for this imagined transformation. I guess it's all a bit of a issue of semantics/definition, I just wanted to point out that the existence or occurence of exceptions does not necessarily void all notions of purity. – AnoE Oct 27 '16 at 05:50
  • 1
    @AnoE But the code I've writted **would** ignore the exception for this imagined transformation. `func(arg)` would return the tuple `(, TheException())`, and so `ignoreThis` will contain the tuple `(, TheException())`, but since we never use `ignoreThis` the exception will have no effect on anything. – Idan Arye Oct 27 '16 at 10:24
1

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 :) ).
SH-
  • 159
  • 2
-1

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

-1

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.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • 1
    The fact that you can rewrite the impurity away does not make the function pure. Haskell is a proof that use of impure functions can be rewritten with pure functions - it uses monads to encode any impure behavior in the return types pure pure functions. The existence of IO monads does not imply that regular IO is pure - why should the existence of exceptions monads imply that regular exceptions are pure? – Idan Arye Oct 27 '16 at 11:12
  • @IdanArye: I'm arguing there is no impurity in the first place. The rewriting example was just to demonstrate that. Regular IO is impure because it causes observable side effects, or may return different values with the same input because of some external input. Throwing an exception does not do any of those things. – JacquesB Oct 27 '16 at 17:45
  • Side-effect free code is not enough. [Referential Transparency](https://en.wikipedia.org/wiki/Referential_transparency) is also a requirement for function purity. – Idan Arye Oct 27 '16 at 18:53
  • @IdanArye: I may be wrong, but can you provide an argument for why an otherwise referentially transparent function which deterministically raises an exception on certain parameter values is not conceptually referentially transparent anymore? It seems there is not total agreement what RT precisely means (http://stackoverflow.com/questions/210835/what-is-referential-transparency/9859966) but I haven't seen any definition which definitely rules out exceptions. – JacquesB Oct 28 '16 at 11:56
  • 1
    Determinism is not enough for referential transparency - side-effects can also be deterministic. Referential transparency means that the expression can be replaced with it's results - so no matter how many times you evaluate referential transparent expressions, in what order, and whether or not you evaluate them at all - the result should be the same. If an exception is deterministically thrown we are good with the "no matter how many times" criteria, but not with the other ones. I've argued about the "whether or not" criteria in my own answer, (cont...) – Idan Arye Oct 28 '16 at 15:43
  • 1
    (...cont) and as for the "in what order" - if `f` and `g` are both pure functions, then `f(x) + g(x)` should have the same result regardless of the order you evaluate `f(x)` and `g(x)`. But if `f(x)` throws `F` and `g(x)` throws `G`, then the exception thrown from this expression would be `F` if `f(x)` is evaluated first and `G` if `g(x)` is evaluated first - this is not how pure functions should behave! When the exception is encoded in the return type, that result would end up as something like `Error(F) + Error(G)` independently of the evaluation order. – Idan Arye Oct 28 '16 at 15:44