Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bridging the Gap between the Functional and the Technical. Think Functional.

0.00/5 (No votes)
18 Sep 2011 1  
Separating the flow of a program from its implementation. Then this flow definition can resemble the one of the functional design. How to this with the use of functions.

Introduction

My previous article was Implementing Continuations in a Generic Way - The Usage of the Option Type in C# - Make Code More Clear. The explanation was a bit concise, because people should concentrate on the code. I think it was too concise, so in this article I will explain a little bit more and also add some additional features in which the concept is brought more to the front.

An essential problem in software development is that a functional design cannot be directly translated into code. In this article, I will try to bridge this gap by concentrating on the point of Business Flow.

For example: You have a firm that deals with Hardware repairs. On certain conditions (Warranty), you get a new one, in others you get a quotation. In a functional design, this can be written down in a use case scenario, or in some diagram. The functional outline is translated by a software developer in all kinds of technical database calls, error handling, Events, Messaging, If-Then logic, etc... If a user asks the functional analyst what happens if a certain condition is met, a look at the diagram will do. If you ask the developer, he has to go through all these calls, from top to bottom, from assembly to assembly to find out. Perhaps he has put all business logic in a Controller (MVC-pattern), but even then the code is full of technical stuff (error handling) and in most cases you still need jumping through multiple methods.

So, I wondered, can you separate the flow of a program from its implementation. In other words, can you make a skeleton of linked subsequent calls that reads from left to right and that tells exactly how the logic is outlined, not how exactly it is technically implemented.

What do you need for this? First, linking calls can only be done if the types are the same. Secondly, the definition must be separate from the execution. The Func<T, T> is the first part of the answer. You have a Function that accepts a (generic) type T and returns the same type T, that can be the input of the next function with the same signature. In the end, you get something like:

Func<Func<T , T>, T>

You can write the same using lambdas and you get:

((x=> x) => X)

Looking at this, it is not difficult to see the problem. It is not an easy read if you got a lot of functions to link. And it become worse if your logic ‘branches’. With this, I mean that you first want to check if a Hardware is Valid, and based on the result (True or False), the logic is branched to either a set of Warranty functions or a set of ResendBackToCustomer-functions. On their turn, both branches can be branched also, etc... The above lambda will become very difficult to read.

To solve this, I wanted to able to define a group of functions, store this in a variable and use this in the end flow. This makes the code readable.

The second part of the solution (splitting definition and execution) can be done because a Func<T, T> can be stored in a collection as a MultiCastDelegate. The collection holds these delegates as definitions but doesn’t execute them immediately. I had to wrap the Funcn<T,T> in something that supports this mechanism.

The final solution has also to take care of exception handling and ‘none’ situations. The latter must happen if your Function<T,T> cannot return a decent T. Compare this with a null value in case of a database. To accomplish this, you can wrap the T into an Option. Option is an abstract class with two concrete implementations Some<T> and None<T>. By returning an Option, you can either return a None or a Some, which speaks for themselves.

To accomplish my goals, I started with the Option and added to it branch functionality, delayed execution, and flow types like IFTHENELSE.

The Option Class. How It Works

First a warning. In traditional OO, you define in one class the data structure and the methods that work on them. In this – more functional inspired approach – you define your data structure in a data object and pipeline this data object through a series of linked functions. As an OO-guy, you have to think differently!

The option supports the following flow types: AndThen, AndThenCaseCheck, AndThenCheck, AndThenIfElse the meaning of which is explained by example. In the accompanying code example, I have a Hardware data object. You first wrap this into an Option:

Option<Hardware> somehw = Option.Some(hw);

And then can do:

Somehw.AndThen(HardwareBusiness.PurchaseWarranty)
......AndThen(HardwareBusiness.ConditionWarranty);

It is not hard to see what this means: First, call the function PurchaseWarranty and then call ConditionWarranty. If you look in the Option class, you see that AndThen only adds the function to an instance of the class Funcbranch (a branch that holds a collection of functions). An option can have one or many Funcbranches. In this case, the above functions are added to the root funcBranch.

You can also do:

FuncBranch<Hardware> fncWarranties = new FuncBranch<Hardware>();
fncWarranties.AndThen(HardwareBusiness.PurchaseWarranty)
              .AndThen(HardwareBusiness.ConditionWarranty);

Somehw.AndThen(determineWarrantyFunc)

In this case, you define a complete independent FunchBranch and add this complete branch to the AndThen flow type. Studying the code in the option class, you see that the passed FuncBranch is added to the collection of FuncBranches. To the root funcBranch we add a dummy function, that’s doing nothing (compare the lambda (x=<x)), but bookmarked with the number of the added funcbranch. If this dummy function is executed, the code hits the bookmark and branches execution to the functions of the added funcBranch. That’s how branching is implemented.

If you call AndThenCaseCheck, you have to apply a list that per list item contains two elements, a function that takes an Option and returns a boolean and a function to apply when the returned boolean is true. The andthenCheck expects the same option/Boolean function and the accompanying apply function in case of a true and of a false. The AndThenIfElse is a bit different. When the First function results in None, the second function is executed. In all these cases, you either provide a single function or a FunchBranch (collection of function). In the following examples, I give you a few compositions you can make.

Somehw.AndThenCheck(HardwareBusiness.isValid
            , (x =>  x) 
            , HardwareBusiness.PurchaseWarranty).Exec();

Means If HardwareBusiness.isValid returns true, do nothing (that’s the meaning of (x=> x)), otherwise execute the function HardwareBusiness.PurchaseWarranty.

somehw.AndThenCheck(x => {
Hardware hw3 = HardwareBusiness.GetHardWare(x);
       return (hw3.Brand== "HP");
       },
       HardwareBusiness.PurchaseWarranty,
       HardwareBusiness.getQuotationFixedPrice
       ).Exec();

Means, you perform an inline check and then proceed with the ontrue (PurchaseWarranty) and onfalse (getQuotationFixedPrice) delegates.

FuncBranch<Hardware> fncWarranties = new FuncBranch<Hardware>();
fncWarranties.AndThen(HardwareBusiness.PurchaseWarranty)
                     .AndThen(HardwareBusiness.ConditionWarranty);

somehw.AndThenCheck(HardwareBusiness.isValid,
       fncWarranties,
       x => {return Option.None<Hardware>();}
       ).Exec();

Means, if the isValid check succeeds, execute the functions of the fncWarranties branch, otherwise return None (meaning stop any further processing). These are just examples that show how you to make your compositions.

Code Example

The accompanying fictitious example - it doesn’t resemble real cases - is about hardware. On the basis of checks, it is determined whether there is a warranty, which pricing model has to be applied and how to swap. Sometimes the checks apply to all, in other cases to some. Sometimes it is determined automatically, sometime the user has to decide. You swap in case of an in-warranty and you make a quotation in case of an out-warranty.

If you run the code, you select a hardware from the list. In the comment, you see what the selected example is about. In the textbox below, you will see a trace of what’s happening. The core of the code is in the class HardwareController, method processHardware. In this method, all the outlining is defined. Compare this method with a traditional approach, where a programmer will code it everywhere.

processHardware is only about outlining the logic first before executing it. For instance, the method caseWarrantyChecksPerMark determines that Sony’s have to be pipelined through the fncWarrantiesExt and the other marks through the fncWarranties. The warranty checks themselves (PurchaseWarranty, etc.) are not executed.

If you look at the individual methods, you see very simple checks that often only set properties. The branching logic is namely already taken care of.

In addition, I implemented a mechanism where all ‘transactional methods’ are not called directly. I grouped these transactions methods in the region Executes of the HardwareBusiness class. The method updateisInvalid should update a database. The class Hardware has a property Executes of type FuncBranch, taking one step further the principle of outlining first before executing. In some cases, this is not a bad thing to do.

Take the following implementation that’s used in the example:

public static Boolean isValid(Option<Hardware> input)
        {
            Hardware hw = GetHardWare(input);
            if (string.IsNullOrEmpty(hw.TypeName))
            {
                Report(hw, "No type -> End flow");
                hw.Executes.AndThen(updateisInvalid);
                hw.Executes.AndThen(sendBack); 
                return false;
            }
            hw.Executes.AndThen(updateValidStatus); 
            return true;
        }

Instead of calling updateValidStatus,updateisInvalid,sendBack directly, these methods are added to hw.Executes, which is of type FuncBranch<Hardware>. The last line in the processHardware method is:

//Execute the executes
    somehw.AndThen(Option.ReadValue<Hardware>(somehw).Executes).Exec();

By now, you probably see what’s happening. hw.Executes returns a FuncBranch, that can be input for the AndThen method of the Hardware Option. The Exec() will execute all this.

In this case, this implementation pays off in case of an error. If you hit the free price scenario, you must enter a price. If you input a non-numerical character, the price will error. Because of this error, the flow that follows will not be executed. In this case, the transactional methods are not called. You don’t need to code for this explicitly, because this logic is part of the Option class.

To the option class, I added a TraceEventHandler. If somebody adds itself as a listener, it will get all the methods that are called. In the example, this is done with these calls.

Option<Hardware> somehw = Option.Some(hw);
somehw.TraceInfo += new Option<Hardware>.TraceEventHandler(somehw_TraceInfo);

Making it extremely simple to trace the calls that make up the flow of the application.
- Bart

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here