Friday, February 6, 2009

Designing C# API in functional style

This post contains slightly unorganized thoughts about usage of functional programming techinques in designing C# APIs.
Lets start from example. Suppose you are building graph layout library which automatically assigns 2D positions to given graph vertices. Interfaces
public interface INode
{
}

public interface IGraph
{
IEnumerable<INode> Nodes { get; }
}

public interface ILayoutInfo
{
Point GetNodePosition(INode node);
}
Let for simplicity IGraph conains just nodes which are target to assign position on a plane. Suppose somebody has implemented methods below
private static IGraph CreateGraph()
{
throw new NotImplementedException();
}

private static ILayoutInfo Layout(this IGraph graph)
{
throw new NotImplementedException();
}

private static void Draw(this IGraph graph, ILayoutInfo layout)
{
// eg. prints each graph node and position pairs
throw new NotImplementedException();
}
Then we can glue those routines in such a way
static void Case1()
{
IGraph graph = CreateGraph();

ILayoutInfo layout = graph.Layout();

graph.Draw(layout);
}
This design works pretty fine. But what I've recently thought is about functional language's functions design style. Here usually function usually augment input paramters data with calculation information itself and returns it as a result. Rewriting method above in such way will lead to something like
static void Case_Chaining()
{
CreateGraph()
  .LayoutNew()
  .Draw();
}
Compare both styles
Traditional styleFunctional (parameters augmentaion) style
General look & feel
static void Case1()
{
IGraph graph = CreateGraph();

ILayoutInfo layout = graph.Layout();

graph.Draw(layout);
}
  • We need dedicated graph reference to pass it to Draw method.
static void Case_Chaining()
{
CreateGraph()
  .LayoutNew()
  .Draw();
}
Layout stuff
  private static ILayoutInfo Layout(this IGraph graph)
  {
      throw new NotImplementedException();
  }
  
  public interface ILayoutedGraph : IGraph, ILayoutInfo
  {
  }

  private static ILayoutedGraph LayoutNew(this IGraph graph)
  {
      throw new NotImplementedException();
  }
  
  • We need dedicated interface ILayoutedGraph which contains NO methods and just compose IGraph and ILayoutInfo
  • LayoutNew returns composed from graph and layout info type.
Drawing stuff
  private static void Draw(this IGraph graph, ILayoutInfo layout)
  {
      // eg. prints each graph node and position pairs
      throw new NotImplementedException();
  }
  
  private static void DrawNew(this ILayoutedGraph layoutedGraph)
  {
      throw new NotImplementedException();
  }
  
DrawNew takes only one paramter.
So what have we got:
  • Functional style looks simpler because we don't need to introduce helper variables to pass data around.
  • ILayoutedGraph contains information about graph and layout. So we can create such convenient things like debugger visualizers. Developer in debug-time can look at variable of ILayoutedGraph type and visually see how it looks like.
    graph debugger visualizer Compare, we can't create such thing in original API style because there are two parts IGraph and ILayoutInfo that holds required data.
  • ILayoutedGraph contains zero methods. We can avoid that by transfering methods from ILayoutInfo into ILayoutedGraph and eliminating ILayoutInfo interface. But ILayoutInfo interface is helpfull by itself for example for mocking purpose.
  • If we had multiple types similar to IGraph (eg. ITree, INetwork) then we need to create multiple layouted types (eg. ILayotedTree, ILayoutedNetwork) which blows up API. It wont happen in original design.
There are couple thoughts around this stuff.
  • There is a particular case when function doesn't augment parameter but returns it unchanged. It is refered as method chaining and Fluent API coding styles when code looks like English sentences (eg. dog.Lies().On().Grass()) For example if BCL type StringBuilder.Append routine returned reference to StringBuilder itself (this) then we could write bld.Append("A").Append("B") (but Append returns nothing, Q: why? A: Why not? ;) ). It extensevely used in many mocking framework eg.Rhino Mocks:
    SetupResult
    .For("MethodName")
    .Return("Hello World");
    
    Hibernate configuration
      Configuration configuration = new Configuration()
      .addClass(typeof(Item))
      .addClass(typeof(Bid))
    
    ). Basicaly Linq itself is all about composing(chaining) Where,OrderBy... methods.
  • Such programming style is used in functional programming languages. But there is no mutable state so function needs to wrap (augment) parameters and calculated data so that user can use both. Does it appropriate to C# APIs?
  • Combining interfaces in single bundle leads to thoughts about mixins.
  • For some coincidence we've got extensions methods in C# which greatly simplify chaining of static routines.
  • For some reason functional programming is discussed together with todays software composability problems. What is the problem with composability in first example? It is composed pretty well :) But there is one issue. I don't know how it called from math point of view but some functions can be called with argument which is a result of calling this function itself. In graph example someone can write graph.LayoutNew().LayoutNew(). Why someone wants to layout graph twice? I don't know. But you can't do such trick with traditional design style.
Questions:
  • What API style traditional or method chaining is better for layouting IGraph?
  • What about blowing up API by creating empty interfaces (like ILayoutedGraph,ILayoutedNetwork etc)?

1 comment:

  1. "Think Before Coding"@stackoverflow.com (http://stackoverflow.com/questions/520043/functional-style-c-api-design-returning-function-parameter-augmented-with-calcu/522922#522922) gave a good idea to use delegation instead of inheritance.

    So instead of
    interface ILayoutedGraph : IGraph,ILayoutInfo {
    }

    which leverage inheritance we use delegation
    interface ILayoutInfoNew : ILayoutInfo {
    IGraph LayoutTarget { get; }
    }

    To better support other graph types we can replace IGraph with generics parameter.

    ReplyDelete