DEV Community

Tomy Durazno
Tomy Durazno

Posted on

Pipe in C#

In functional programming you can express a program in terms of a flow of functions that takes an initial input and manipulates it in order to achieve the desired output. Each function is going to receive as argument the return value of the previous function in the flow chain, making a program that is easy to read and easy to understand what it does at a first glance

Lets look at a simple example written in F#:

seq { for x in 1 .. 10 -> x }
|> Seq.map(fun n -> n * 2) 
|> Seq.filter(fun n -> n > 5) 
|> Enumerable.Sum
|> Console.WriteLine
  • The first statement is going to create a sequence of numbers from 1 to 10
  • The second statement is going to multiply each number by 2
  • The third statement is going to remove from the sequence all the numbers that are less than 5
  • The fourth statement is going to sum all numbers in the sequence
  • The last statement is going to output the result to the Console

This is easily achievable because F# has the "|>" operator, that lets us write this type of expressions

But how can we do that in C#, since the language doesn't have this magic operator?

Well, we can build something like that! In order to do it, we first must solve some problems with types

in C#, as in any strongly-typed language, we must know at compile time the types that a function recieves and returns. We can use the 'dynamic' keyword, but that would eliminate the type checking and it doesn't work with some of the other language features (like extension methods, for example).

Ideally, I would like to have all the benefits of type checking but I dont want to have to write all those types. So...

Roslyn To The Rescue!

The new C# compiler, codename 'Roslyn', does a great job at infering the type that a expression returns. So, combining the type inference with some good ol' generics, we can write something like this:

public static C Pipe<A,B,C>(this A obj, Func<A,B> func1, Func<B,C> func2) => func2(func1(obj)); 

This is an extension method that can be invoked over an instance of type A, and receives as arguments 2 functions, one that takes an A typed instance and returns a B typed, and the second takes a B typed object and returns a C typed one.

By declaring the types this way, the compiler can infer all arguments and return types from the functions. We can go one step further and create declarations of the Pipe function that recieves n number fo functions to be "piped":

public static E Pipe<A,B,C,D,E>(this A obj, Func<A,B> func1, Func<B,C> func2, Func<C,D> func3, Func<D,E> func4) => func4(func3(func2(func1(obj))));

And with this function we can write a program in C# that is equivalent to the one written in F#, not only in behaviour but also in how it looks:

Enumerable.Range(1,10)
      .Pipe(
r => Enumerable.Select(r, n => n * 2),
r => Enumerable.Where(r, n => n > 5),
Enumerable.Sum,
n => { Console.WriteLine(n); return n; }); 
//Since WriteLine returns 'void'

Since we dont have to annotate the function's types, we can go a little 'black magic' and push the type inference to the next level and write a function that returns an anonymous type:

"app.txt".Pipe(
MyUtils.ReadTxtFromDesktop,
string.Concat,
XDocument.Parse, 
n => n.ToXmlDocument(),
n => n.SelectSingleNode("appSettings").ChildNodes.GetNodes(),
//Returns an anonymous object! 
n => n.Select(x => new { Key = x.Attributes["key"].InnerText, Value= x.Attributes["value"].InnerText}),
d => d.Select(a => $"public string { a.Key}  = { a.Value };"));

In this last example, is quite intuitive to understand what the code does:

  • Reads a Txt file from the desktop
  • Concatenates all its lines to a single string
  • Calls 'XDocument.Parse' to convert it

And then pipes some functions to end with the desired output, in the middle invoking a function that returns an anonymous object!

The fact that we are not annotating all the types doesn't mean that the compiler is not doing the type checking:

"app.txt".Pipe(
MyUtils.ReadTxtFromDesktop,
XDocument.Parse, 
n => n.ToXmlDocument(),
n => n.SelectSingleNode("appSettings").ChildNodes.GetNodes(),
//Returns an anonymous object! 
n => n.Select(x => new { Key = x.Attributes["key"].InnerText, Value= x.Attributes["value"].InnerText}),
d => d.Select(a => $"public string { a.Key}  = { a.Value };"));

This last example doesn't compile:

Alt Text

Here is a link to a repo that contains a handful of 'Pipe' declarations (up to 25 functions): https://github.com/TomyDurazno/PipeExtensions

  • I wrote all the code snippets in this article using LINQPad, a phenomenal .NET IDE/REPL/ORM/Swiss Knife/Whatever that I use almost everyday for the most diverse tasks. Go take a look at https://www.linqpad.net/ and become a LINQPad junkie just like myself!

Thanks and have fun coding in a more declarative way!

Latest comments (2)

Collapse
 
winstonpuckett profile image
Winston Puckett • Edited

So... I just released a NuGet package which is very similar to this 3 days ago. Then went back on proposal 96 to include it in my dev post... Then noticed there's a comment linking back to this post and the GitHub repo. I feel kinda bad because we approached it from a similar attitude, but I really didn't know this was out there and I'm hoping that since this post is from 2019, you weren't hoping to publish this on NuGet yourself. I think you probably still could haha. Either way, I mentioned you in my readme, because I feel like it's fair since you've done the work as well, and it looks like a good implementation.

Here's the repo if you're curious: github.com/winstonpuckett/WinstonP...

Collapse
 
tomydurazno profile image
Tomy Durazno

Hey! I havent read this! I didnt publish to Nuget, thanks for adding me to your readme !