3

Note, if you think this post is long, over half of it is just reference code, for completion

I have started looking at functional programming in C#, mostly through this book and this tutorial

So I have implemented the Option, with extension method Map and Bind. So far so good. Lets say that I want to build a pipeline with some filtering in the beginning and then call a async method, then continue the processing with the result. For example:

Ok, this pseudo code isnt super useful, but hopefully its shows what I mean. I want to first make chainable "if name == Kalle"-substitute. Then if so, get products from a async method and some more processing. The first Map, however will return a Option<Task> and I have to await it inside second Map... not pretty.

Option<Customer> cust = Some(new Customer(){Name ="Kalle"});
     cust.Bind(NamedKalle)  //yes I could use where here, not the point though :)
    .Map(async c => await myHttpClient.GetAdressAsync(c))
    .Map(addressTask=> (await addressTask).DoSomethingMore(address))
//...

public Customer NamedKalle(Customer c) =>
   c.Name == "Kalle" ? Some(c) : None;

I have managed to make an async Match and async Map, which seem to work, but I haven't seen this, which leads me to believe that I'm missing something. Can I use the original map and bind in some way?

 // Match that awaits (member on Option<T>)
   public async Task<R> MatchAsync<R>(Func<R> noneFunc, Func<T, Task<R>> someFunc) =>
            IsSome 
                ? await someFunc(_value)
                : await Task.FromResult(noneFunc());

 // Map that awaits, extension method 
  public static async Task<Option<R>> MapAsync<T, R>(this Option<Task<T>> optT, Func<T, R> func) =>
            await optT.MatchAsync(
                ()=> F.None, 
                async v => F.Some<R>(func(await v)));

BELOW IS JUST FOR REFERENCE, ITs an Implementation of Option, helpers and Extension method. This is a subset of the open source project at https://github.com/la-yumba/functional-csharp-code/

namespace ProductCatalog.Api.Functional
{
    public static class FunkyExtensions
    {
        public static Option<R> Map<T, R>(this Option<T> opt, Func<T, R> func) =>
            opt.Match(() => F.None, v => F.Some<R>(func(v)));

        public static async Task<Option<R>> Map2<T, R>(this Option<Task<T>> optT, Func<T, R> func) =>
            await optT.Match2(
                ()=> F.None, 
                async v => F.Some<R>(func(await v)));

        public static Option<T> Where<T>(this Option<T> opt, Func<T, bool> pred) =>
            opt.Match(() => F.None, 
                      t => pred(t) ? opt: F.None);


        public static Option<R> Bind<T, R>(this Option<T> opt, Func<T, Option<R>> func) =>
            opt.Match(() => F.None, (v) => func(v));

    }

    public static partial class F
    {
        public static Option<T> Some<T>(T value) => new Option.Some<T>(value); // wrap the given value into a Some
        public static Option.None None => Option.None.Default;  // the None value
    }

    public struct Option<T>
    {
        T _value;
        public bool IsSome { get; set; }
        public bool IsNone => !IsSome;
        public override string ToString() => IsNone ? $"({_value})" : $"({_value})";
        private Option(T value)
        {
            if (value == null)
                throw new Exception("Cant be null");

            _value = value;
            IsSome = true;
        }

        public static implicit operator Option<T>(Option.None _) => new Option<T>();
        public static implicit operator Option<T>(Option.Some<T> some) => new Option<T>(some.Value);
        public R Match<R>(Func<R> noneFunc, Func<T, R> someFunc) =>
            IsSome ? someFunc(_value)
                : noneFunc();

        public async Task<R> Match2<R>(Func<R> noneFunc, Func<T, Task<R>> someFunc) =>
            IsSome 
                ? await someFunc(_value)
                : await Task.FromResult(noneFunc());

    }

    namespace Option
    {
        public struct None
        {
            internal static readonly None Default = new None();
        }
        public struct Some<T>
        {
            public T Value { get; set; }
            public Some(T value)
            {
                if (value == null)
                    throw new Exception("Value should not be null when creating Some, use None instead");
                Value = value;
            }
        }
    }
}
Cowborg
  • 2,645
  • 3
  • 34
  • 51
  • What is `IsSome`? Where is it defined? – kiner_shah Jan 19 '22 at 09:10
  • Hi kiner_shah. I just didnt want to bloat the question with alot of refernce code, but fair question. IsSome is if the T in Option has a value. I pasted all (stolen) implementation of Option as well – Cowborg Jan 19 '22 at 09:20
  • 1
    There are several monads in C# (Lazy, Task, IEnumerable, etc), and you can write your own; but I believe what you're looking for is higher-order function support, which C# as a language is not going to be very helpful with, since it is not a functional language. Feel free to use *some* functional techniques (railway programming is sometimes useful), but if you're trying to make C# into F# you'll just end up frustrated. Lack of higher-order functions and lack of (non-trivial) tail-call elimination come immediately to mind. – Stephen Cleary Jan 19 '22 at 22:25
  • @StephenCleary, no higher-order functions? – ChiefTwoPencils Jan 20 '22 at 07:41
  • 1
    @StephenCleary: Im a big fan of you (and your book...and most of your posts here on SO)... however, this is not really a question of if I should use functional techniques in C# or not (yes, F# is nice, but not used nearly as much as C#). Hopefully more people read Enrico Buonannos book "Functional programming in C# 2nd ed." and also check out the https://github.com/louthy/language-ext and spread the word. However, I dont want this to be a side discussion from my original question – Cowborg Jan 20 '22 at 07:45
  • 1
    That book is my absolute favorite! There's a new edition out refactored for the new features that better support FP. I was lucky enough to be chosen as a Manning reviewer for it; it's about to be delivered any day. – ChiefTwoPencils Jan 20 '22 at 07:49
  • @StephenCleary I thought .NET's `Task`, like [JavaScript's `Promise`](https://buzzdecafe.github.io/2018/04/10/no-promises-are-not-monads), isn't a _true_ s̵c̵o̵t̵s̵m̵a̵n̵ monad? – Dai Mar 31 '22 at 01:49
  • @Dai: It's conceptually a monad. Its `bind` is using `async` and `await`, so it's not a *function*, but it's essentially the same thing. If you relax the definition of a monad so it doesn't assume a functional language, it fits. – Stephen Cleary Mar 31 '22 at 01:57

0 Answers0