In the previous posts of this series we have seen how functional error handling can be implemented in F# and C#.
Now let's look at an implementation of the command line parser using the Notification Pattern.
Parsing arguments with the Notification Pattern
All the dependencies, in this case the arguments and the argument infos, are provided as constructor parameters.
The class exposes a public read-only property Dictionary
which has a private backing field. In the getter the dictionary will be created if the backing field is NULL. If the creation of the dictionary fails, an exception will be thrown.
The caller can check if the arguments are valid by calling the Validation
method. This method will return a Notification
instance which is a container for error messages. The Notification
instance has a property IsValid
that is true if the validation succeeded, otherwise it is false.
Here is a shortened version of the class:
public class NoteArguments
{
private readonly IEnumerable<string> _commandLineArguments;
private readonly IEnumerable<string> _requiredCommands;
private readonly IEnumerable<string> _definedCommands;
private Dictionary<string, string> _dictionary;
public NoteArguments(IEnumerable<string> commandLineArguments, IEnumerable<ArgInfo> defs)
{
_commandLineArguments = commandLineArguments;
_requiredCommands = defs.Where(x => x.required).Select(x => x.command);
_definedCommands = defs.Select(x => x.command);
}
public Dictionary<string, string> Dictionary
{
get
{
return _dictionary ?? (_dictionary = CreateDictionary());
}
}
private Dictionary<string, string> CreateDictionary()
{
Check();
return _dictionary;
}
private void Check()
{
var note = Validation();
if (note.HasErrors)
throw new ArgumentException(note.ErrorMessage);
}
public Notification Validation()
{
var note = new Notification();
var parsed = Parse(note);
if (note.HasErrors)
return note;
CheckNoDuplicates(parsed, note);
CheckAllRequired(parsed, note);
CheckAllDefined(parsed, note);
if (note.IsValid)
_dictionary = parsed.ToDictionary(x => x.First(), x => x.Skip(1).First());
return note;
}
//...
}
There are numerous other possibilities of how to implement this class and its public interface depending on the context. Returning a Notification
instance might not really be necessary because in this case there is no separated presentation layer. So the IsValid
or Errors
properties and the Validate
method could be members of the NoteArguments
class itself. Or we could also implement an interface IValidatable<T>
which exposes those members. Also the validator could be injected. However, since all implementations are in essence very similar I tried to stick with a very basic approach for demonstration purposes.
It's easy to shoot yourself in the foot
So what is the drawback of this design?
This design makes it very easy to shoot yourself in the foot.
If we access the Dictionary
property while the provided command line parameters are not valid, the application will crash.
Can we avoid this? - No, we can't avoid this because we don't know what to return in case of a failure. Off course we could return NULL or even a NULL object, but this only postpones the need to handle the failure case. The call side has to wrap the calling code into a try-catch
statement or check for null.
And the real problem with this is that the caller is not forced to do that. Also it might not even be obvious for the caller that accessing a given property could crash the application. Furthermore, exceptions, try-catch
statements, and NULL checks will decrease the readability and maintainability of the code and will ruin the possibility to reason about the code.
> @davetchepak "What can C# do that F# cannot?" NullReferenceException 🙂
— Tomas Petricek (@tomaspetricek) 21. März 2013
Exceptions and expected behavior
Martin Fowler states that exception are only for unexpected behaviors:
Exceptions signal something outside the expected bounds of behavior of the code in question. But if you're running some checks on outside input, this is because you expect some messages to fail - and if a failure is expected behavior, then you shouldn't be using exceptions. -- Martin Fowler
And still the notification pattern uses exceptions for an expected failure case. As we have seen, we can not really avoid an exception when accessing the Dictionary
property while the provided command line parameters are not valid. But this is not an unexpected situation because the command line parameters are outside inputs, so it is not unlikely that something goes wrong.
A better way of doing error handling
Returning NULL or a NULL object goes into the right direction, even though that's still far away from a good and solid design.
If we continue to think this approach we will eventually end up with the idea of the Result
type as implemented by the Chessie library. The details of this approach are described in the first three parts of this series.
By comparing the functional error handling approach to the imperative approach it will become even more clear what the benefits of the Result
type are.
Because it is very hard and inconvenient to extract the inner value of a Result
without using its provided methods, the caller is always forced to handle the success as well as the failure case. There is no need to throw exceptions or to return NULL. Therefore the code won't be cluttered with try catch statements or NULL checks. Instead the caller can use the composition mechanisms of functional error handling which is applicative and monadic composition. As a result the code will be more declarative. The composition mechanisms will always be the same and they scale through all the layers of the application.
The FsCQRSShop and Railway-Oriented-Programming-Example are only two examples that demonstrate functional error handling and railway oriented programming without any exceptions where the data flows on two tracks throughout the application.
Conclusion
In the first three posts of this series we have examined the details of functional error handling with the help of the library Chessie in F# and C#. In C#, though, functional error handling is not as comfortable as it is in F# because of missing language features. Additionally, the possibility to initialize a Result
value with NULL in C# ruins the benefit of safety that we have in F#. But still if C# and F# projects have to inter-operate, it does work fine with the Chessie library.
In this post we briefly compared the functional to the imperative approach.
We have seen the benefits of functional error handling, which are:
- A much safer and solid usage
- More predictable code
- The details of error handling are abstracted away
- More declarative code
- Functional error handling embraces composition, separation of concerns and scalability
Resources
- Slides and video on ROP
- Railway oriented programming
- Chessie
- Refactoring to monadic C# - applicative functors
- Applicative functors: definition and syntax
- Beyond the Monad fashion (I.): Writing idioms in LINQ
- Visualization of Applicatives
- Map and Bind and Apply, Oh my!
- Understanding continuations
- Applicative Laws
- Replacing Throwing Exceptions with Notification in Validations
- Idiomatic or idiosyncratic?
- Command line parser sample code
- Code samples in F# and C#
- FsCQRSShop
- Railway-Oriented-Programming-Example