Click here to Skip to main content
15,879,326 members
Articles / Programming Languages / C#

Using Tuples to Synthesize Polyadic Returns in C# and COBOL

Rate me:
Please Sign up or sign in to vote.
4.71/5 (7 votes)
18 Jan 2010CC (ASA 2.5)5 min read 29.2K   7   11
The introductions of Tuples (originally to support F#) into .NET shows a possible way to use polyadic returns in C#, COBOL etc., without syntax enhancement.

Introduction

As I have mentioned before, the lack of polyadic returns from many supposedly modern languages acts a a barrier to their future usefulness. However, the introduction of Tuples (originally to support F#) into .NET shows a possible way to retrofit this feature without syntax enhancement. I demonstrate the technique in C# and COBOL.

Why do we have function signatures like this:

C#
void divideAndRemainder(int numerator, int denominator,ref int div, ref int remain)

The result of the function is obviously polyadic, it has two distinct parts. Because our programming language is not set up to handle polyadic returns, we pass in parameters by reference to act as return values. This is a big fat hack. One or the other, but not both please. What would be great is something like this:

C#
void (int ref, int remain)divideAndRemainder(int numerator, int denominator)

But we cannot have that in C#, VB, Java, COBOL ....

In F#, we do have native syntax for tuples:

fs
let someFunc= (1,"hello world")

The above will produce a strongly typed tuple which contains two items: the first an integer, and the second a string. The function is polyadic. Not only that - but it is implemented on the Common Language Runtime. So, it must be possible to access whatever structures F# is using from other .NET languages, and it is. OK, the result is a bit clunky, but it is a first step.

To illustrate, I will use a very simple example: a function to divide an integer by an integer and get the result and remainder. This example shows two huge advantages of the tuple approach.

  1. It gets rid of the need for reference parameters to synthesize polyadic returns.
  2. It allows for in-line encapsulation of data by functions, making the role of variables less complex in code, and thus making the code closer to a functional description of what it is actually doing (more self documenting).

Here is the classic C# approach to the example:

C#
    ...
    // Example 4: The horrid way
    int local2 = 26;
    int local3 = 4;
    int local4 = 0;
    var local5 = divAndRemainderHorrid(local2, local3,ref local4);
    System.Console.WriteLine
    (
            "Dividing " + local2 +
            " by " + local3 +
            " gives " + local5 +
            " remainder " + local4
    );
    ...
    static int divAndRemainderHorrid(int numerator, 
               int denominator, ref int remainder)
    {
        remainder = numerator % denominator;
        return numerator / denominator;
    }
}

There is nothing exactly wrong with this; it is just a mess. The function returns some of its results and passes back some of it via a reference variable. I guess one could make the function void and pass back everything via reference parameters - but that does not seem like much of an improvement. Also, there has to be heavy use of locals to manage all the intermediate storage. This makes chaining of the function near impossible. E.g., we could not make a function WriteResult which encapsulates the System.Console.Write line code and call that with the return from the divide/remainder function without marshalling all the intermediates via local variables.

Doing it with Tuples

F# tuples are implemented under the covers using System.Tuple. This type has a set of generic factor methods Create(...) which allow the generation of strongly typed tuples of up to 7 elements. It is possible to make longer tuples by chaining them (putting tuples in tuples). F# does this chaining for us; however, in other languages, this has to be done by hand, which makes longer tuples rather clunky. See http://msdn.microsoft.com/en-us/library/system.tuple(VS.100).aspx.

Rather than write lots of text describing tuples further, I have illustrated their use via code.

Example 1 shows a simple example of using tuples so that dividend and remainder are returned in one tuple, thus avoiding the use of a reference parameter. Example 2 shows how we can make the function return a tuple of the passed in parameters and the results so that all the information about the operation is encapsulated. This then allows for simple access to this encapsulated information.

Example 3 is the really interesting one. It shows that by fully encapsulating all the information about a function, it is possible to dispense with local temporary storage (variables) altogether. Example 3 shows how using tuples has allowed us to write code which exactly expresses the actions being performed without having to have loads of operational clutter getting in the way!

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tuples
{
    class Program
    {
        static void Main(string[] args)
        {
            // Example 1: Avoiding reference types
            System.Console.WriteLine("Result=" + DivAndRemainder (10 , 3));

            // Example 2: Avoiding reference types and encapsulation
            var local1 = DivAndRemainderKeepAll(26, 4);
            System.Console.WriteLine
            (
                    "Dividing " + local1.Item1 +
                    " by " + local1.Item2 +
                    " gives " + local1.Item3 +
                    " remainder " + local1.Item4
            );

            // Example 3: Function chaining
            WriteResult(DivAndRemainderKeepAll(26, 4));

            // Example 4: The horrid way
            int local2 = 26;
            int local3 = 4;
            int local4 = 0;
            var local5 = DivAndRemainderHorrid(local2, local3,ref local4);
            System.Console.WriteLine
            (
                    "Dividing " + local2 +
                    " by " + local3 +
                    " gives " + local5 +
                    " remainder " + local4
            );
        }

        static Tuple<int, int> DivAndRemainder(int numerator, int denominator)
        {
            return Tuple.Create(numerator / denominator, numerator % denominator);
        }

        static Tuple<int,int,int, int> 
               DivAndRemainderKeepAll(int numerator, int denominator)
        {
            return Tuple.Create
            (
                numerator,
                denominator,
                numerator / denominator,
                numerator % denominator
            );
        }

        static int DivAndRemainderHorrid(int numerator, 
                   int denominator, ref int remainder)
        {
            remainder = numerator % denominator;
            return numerator / denominator;
        }

        static int WriteResult(Tuple<int, int, int, int> result)
        {
            System.Console.WriteLine
            (
                "Dividing " + result.Item1 +
                " by " + result.Item2 +
                " gives " + result.Item3 +
                " remainder " + result.Item4
            );
        }
    }
}

Doing it with COBOL

Micro Focus COBOL for .NET has full support for generics with a simply syntax. This allows us to use Tuples in a straightforward way:

COBOL
procedure division 
using by value numerator as 
binary-long denominator as binary-long 
returning result as 
type System.Tuple[binary-long binary-long binary-long binary-long].

Above, we can see the signature definition for the DivAndRemainderKeepAll method. This is equivalent to the C#:

C#
static Tuple<int,int,int, int> DivAndRemainderKeepAll(int numerator, int denominator)

Which is 'better' is simply a question of taste. Also, we can note that they are fully interoperable. So the whole of example 3 in COBOL is here:

COBOL
class-id. "Tuples".

   method-id. Main static.
   procedure division.
       invoke Tuples::WriteResult
       (
           Tuples::DivAndRemainderKeepAll(26 4)
       )
   end method.
   
   method-id. DivAndRemainderKeepAll static.
       01 div binary-long.
       01 rem binary-long.
   procedure division 
       using by value numerator as 
       binary-long denominator as binary-long 
       returning result as 
       type System.Tuple[binary-long binary-long binary-long binary-long].
           compute div = numerator / denominator
           compute rem = function mod(numerator denominator)
       set result to type System.Tuple::Create(numerator denominator div rem)
   end method.
   
   method-id. WriteResult static.
   procedure division 
       using by value result 
       as type System.Tuple[binary-long binary-long binary-long binary-long].
       invoke type System.Console::WriteLine
       (
           String::Format
           (
               "Dividing {0} by {1} gives {2} remainder {3} "
               result::Item1 
               result::Item2 
               result::Item3 
               result::Item4 
           )
       )
   end method.
end class.

Multi-Core and the Cloud - Why this Really Matters

So far, I have presented some nice syntactic reasons for using Tuples specifically, and polyadic returns in general. There is a really major, huge, looming and life critical reason why we - as developers and architects - need to start thinking this way. Hardware is changing - processing distribution is ascendant!

The problem is that passing in reference parameters just to allow polyadic return does not actually express the intent of the programmer. The compiler and runtime must understand the reference parameter has a value which exists outside the scope of the function to which it is passed. It can therefore be updated outside that scope, and updates inside that scope should be visible outside it. These scoping effects are not the intention of the programmer, but the compiler and runtime do not 'know' this.

In a massively multi-core or distributed cloud environment, we might want to execute a function on a different core from the one on which it is called. If the function does not reference any external storage (class, object, or calling function local variables), then there is no reason this cannot be done. However, the reference parameters mean that the compiler and runtime 'think' the function does reference external storage and so they cannot efficiently move execution onto a different core.

Conclusions

The tuples based patterns used here show how to overcome a major drawback of many traditional programming languages (the lack of polyadic returns) and so pave the way to more efficient program implementations in current and upcoming environments. Where we can expect languages such as COBOL and Algol's children (C#, Java etc.) to continue to do the heavy lifting of much of the computing, these techniques could prove a very useful way forward until such time as explicit polyadic types are added into the syntaxes themselves.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License


Written By
Web Developer
United Kingdom United Kingdom
I am now a Software Systems Developer - Senior Principal at Micro Focus Plc. I am honoured to work in a team developing new compiler and runtime technology for Micro Focus.

My past includes a Ph.D. in computational quantum mechanics, software consultancy and several/various software development and architecture positions.

For more - see

blog: http://nerds-central.blogspot.com

twitter: http://twitter.com/alexturner

Comments and Discussions

 
GeneralPolyadic or Polygot Pin
Richard Ashman26-Jan-10 2:25
Richard Ashman26-Jan-10 2:25 
GeneralRe: Polyadic or Polygot Pin
alex turner26-Jan-10 2:42
alex turner26-Jan-10 2:42 
GeneralRe: Polyadic or Polygot Pin
Richard Ashman26-Jan-10 4:29
Richard Ashman26-Jan-10 4:29 
GeneralRe: Polyadic or Polygot Pin
alex turner26-Jan-10 4:40
alex turner26-Jan-10 4:40 
GeneralDIVIDE (not the main issue of the article) Pin
Pablo Aliskevicius25-Jan-10 23:08
Pablo Aliskevicius25-Jan-10 23:08 
GeneralRe: DIVIDE (not the main issue of the article) Pin
alex turner25-Jan-10 23:15
alex turner25-Jan-10 23:15 
GeneralIt's .NET 4.0 beta Pin
MollyTheCoder25-Jan-10 17:50
MollyTheCoder25-Jan-10 17:50 
GeneralRe: It's .NET 4.0 beta Pin
alex turner25-Jan-10 23:07
alex turner25-Jan-10 23:07 
GeneralNice article Pin
Nicholas Butler19-Jan-10 2:03
sitebuilderNicholas Butler19-Jan-10 2:03 
GeneralYou could have returned a struct Pin
chrwal18-Jan-10 11:36
chrwal18-Jan-10 11:36 
GeneralRe: You could have returned a struct Pin
alex turner18-Jan-10 22:18
alex turner18-Jan-10 22:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.